python 獲取網頁的內容
1.安裝pip
我的個人桌面系統用的linuxmint,系統預設沒有安裝pip,考慮到後面安裝requests模組使用pip,所以我這裡第一步先安裝pip。
1 |
|
安裝成功,檢視PIP版本:
1 |
|
2.安裝requests模組
這裡我是通過pip方式進行安裝:
1 |
|
執行import requests,如果沒提示錯誤,那說明已經安裝成功了!
檢驗是否安裝成功
3.安裝beautifulsoup4
Beautiful Soup 是一個可以從HTML或XML檔案中提取資料的Python庫。它能夠通過你喜歡的轉換器實現慣用的文件導航,查詢、修改文件的方式。Beautiful Soup會幫你節省數小時甚至數天的工作時間。
1 |
|
注:這裡我使用的是python3的安裝方式,如果你用的是python2,可以使用下面命令安裝。
1 |
|
4.requests模組淺析
1)傳送請求
首先當然是要匯入 Requests 模組:
1 |
|
然後,獲取目標抓取網頁。這裡我以下為例:
1 |
) |
這裡返回一個名為 r 的響應物件。我們可以從這個物件中獲取所有我們想要的資訊。這裡的get是http的響應方法,所以舉一反三你也可以將其替換為put、delete、post、head。
2)傳遞URL引數
有時我們想為 URL 的查詢字串傳遞某種資料。如果你是手工構建 URL,那麼資料會以鍵/值對的形式置於 URL 中,跟在一個問號的後面。例如, cnblogs.com/get?key=val。 Requests 允許你使用 params 關鍵字引數,以一個字串字典來提供這些引數。
舉例來說,當我們google搜尋“python爬蟲”關鍵詞時,newwindow(新視窗開啟)、q及oq(搜尋關鍵詞)等引數可以手工組成URL ,那麼你可以使用如下程式碼:
1 2 3 |
|
3)響應內容
通過r.text或r.content來獲取頁面響應內容。
1 2 3 4 5 |
|
Requests 會自動解碼來自伺服器的內容。大多數 unicode 字符集都能被無縫地解碼。這裡補充一點r.text和r.content二者的區別,簡單說:
resp.text返回的是Unicode型的資料;
resp.content返回的是bytes型也就是二進位制的資料;
所以如果你想取文字,可以通過r.text,如果想取圖片,檔案,則可以通過r.content。
4)獲取網頁編碼
1 2 3 4 5 |
|
5)獲取響應狀態碼
我們可以檢測響應狀態碼:
1 2 3 4 5 |
|
5.案例演示
並且只抓取頁面中文章標題和內容等有用資訊。
演示環境
作業系統:linuxmint
python版本:python 3.5.2
使用模組:requests、beautifulsoup4
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
一下為獲取網頁上特定的內容的程式 沒有註釋
import requests
from bs4 import BeautifulSoup
import bs4
import os
from time import sleep
url_list = []
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
def url_all():
for page in range(1,401):
url = 'http://blog.csdn.net/?ref=toolbar_logo&page='+str(page)
url_list.append(url)
def essay_url(): #找到所有文章地址
blog_urls = []
for url in url_list:
html = requests.get(url, headers=headers)
html.encoding = html.apparent_encoding
soup = BeautifulSoup(html.text, 'html.parser')
for h3 in soup.find_all('h3'):
blog_url = (h3('a')[0]['href'])
blog_urls.append(blog_url)
return blog_urls
def save_path():
s_path = 'D:/blog/'
if not os.path.isdir(s_path):
os.mkdir(s_path)
else:
pass
return s_path
def save_essay(blog_urls,s_path): #找到所有文章標題,文章內容。
for url in blog_urls:
blog_html = requests.get(url, headers=headers)
blog_html.encoding = blog_html.apparent_encoding
soup = BeautifulSoup(blog_html.text, 'html.parser')
try:
for title in soup.find('span', {'class': 'link_title'}):
if isinstance(title, bs4.element.Tag):
print('-----文章標題-----:', title.text)
blogname = title.text
blogname = blogname.replace("\n",'')
blogname = blogname.replace("\r",'')
blogname = blogname.replace(" ",'')
try:
file = open(s_path + str(blogname) + '.txt', 'w')
file.write(str(title.text))
file.close()
except BaseException as a:
print(a)
for p in soup.find('div', {'class': 'article_content'}).children:
if isinstance(p, bs4.element.Tag):
try:
file = open(s_path + str(blogname) + '.txt', 'a')
file.write(p.text)
file.close()
except BaseException as f:
print(f)
except BaseException as b:
print(b)
print('---------------所有頁面遍歷完成----------------')
sleep(10)
url_all()
save_essay(essay_url(),save_path())
程式碼
讀入網頁加以解析抓取,需要用到的軟體包是 requests_html 。我們此處並不需要這個軟體包的全部功能,只讀入其中的 HTMLSession 就可以。
from requests_html import HTMLSession
然後,我們建立一個會話(session),即讓Python作為一個客戶端,和遠端伺服器交談。
session = HTMLSession()
前面說了,我們打算採集資訊的網頁,是《如何用《玉樹芝蘭》入門資料科學?》一文。
我們找到它的網址,儲存到url變數名中。
url = 'https://www.jianshu.com/p/85f4624485b9'
下面的語句,利用 session 的 get 功能,把這個連結對應的網頁整個兒取回來。
r = session.get(url)
網頁裡面都有什麼內容呢?
我們告訴Python,請把伺服器傳回來的內容當作HTML檔案型別處理。我不想要看HTML裡面那些亂七八糟的格式描述符,只看文字部分。
於是我們執行:
print(r.html.text)
這就是獲得的結果了:
我們心裡有數了。取回來的網頁資訊是正確的,內容是完整的。
好了,我們來看看怎麼趨近自己的目標吧。
我們先用簡單粗暴的方法,嘗試獲得網頁中包含的全部連結。
把返回的內容作為HTML檔案型別,我們檢視 links 屬性:
r.html.links
這是返回的結果:
這麼多連結啊!
很興奮吧?
不過,你發現沒有?這裡許多連結,看似都不完全。例如第一條結果,只有:
'/'
這是什麼東西?是不是連結抓取錯誤啊?
不是,這種看著不像連結的東西,叫做相對連結。它是某個連結,相對於我們採集的網頁所在域名(https://www.jianshu.com)的路徑。
這就好像我們在國內郵寄快遞包裹,填單子的時候一般會寫“XX省XX市……”,前面不需要加上國家名稱。只有國際快遞,才需要寫上國名。
但是如果我們希望獲得全部可以直接訪問的連結,怎麼辦呢?
很容易,也只需要一條 Python 語句。
r.html.absolute_links
這裡,我們要的是“絕對”連結,於是我們就會獲得下面的結果:
這回看著是不是就舒服多了?
我們的任務已經完成了吧?連結不是都在這裡嗎?
連結確實都在這裡了,可是跟我們的目標是不是有區別呢?
檢查一下,確實有。
我們不光要找到連結,還得找到連結對應的描述文字呢,結果裡包含嗎?
沒有。
結果列表中的連結,都是我們需要的嗎?
不是。看長度,我們就能感覺出許多連結並不是文中描述其他資料科學文章的網址。
這種簡單粗暴直接羅列HTML檔案中所有連結的方法,對本任務行不通。
那麼我們該怎麼辦?
我們得學會跟 Python 說清楚我們要找的東西。這是網頁抓取的關鍵。
想想看,如果你想讓助手(人類)幫你做這事兒,怎麼辦?
你會告訴他:
“尋找正文中全部可以點選的藍色文字連結,拷貝文字到Excel表格,然後右鍵複製對應的連結,也拷貝到Excel表格。每個連結在Excel佔一行,文字和連結各佔一個單元格。”
雖然這個操作執行起來麻煩,但是助手聽懂後,就能幫你執行。
同樣的描述,你試試說給電腦聽……不好意思,它不理解。
因為你和助手看到的網頁,是這個樣子的。
電腦看到的網頁,是這個樣子的。
為了讓你看得清楚原始碼,瀏覽器還特意對不同型別的資料用了顏色區分,對行做了編號。
資料顯示給電腦時,上述輔助可視功能是沒有的。它只能看見一串串字元。
那可怎麼辦?
仔細觀察,你會發現這些HTML原始碼裡面,文字、圖片連結內容前後,都會有一些被尖括號括起來的部分,這就叫做“標記”。
所謂HTML,就是一種標記語言(超文字標記語言,HyperText Markup Language)。
標記的作用是什麼?它可以把整個的檔案分解出層次來。
(圖片來源:https://goo.gl/kWCqS6)
如同你要傳送包裹給某個人,可以按照“省-市-區-街道-小區-門牌”這樣的結構來寫地址,快遞員也可以根據這個地址找到收件人。
同樣,我們對網頁中某些特定內容感興趣,可以依據這些標記的結構,順藤摸瓜找出來。
這是不是意味著,你必須先學會HTML和CSS,才能進行網頁內容抓取呢?
不是的,我們可以藉助工具,幫你顯著簡化任務複雜度。
這個工具,Google Chrome瀏覽器自帶。
我們在樣例文章頁面上,點選滑鼠右鍵,在出現的選單裡面選擇“檢查”。
這時,螢幕下方就會出現一個分欄。
我們點選這個分欄左上角(上圖紅色標出)的按鈕。然後把滑鼠懸停在第一個文內連結(《玉樹芝蘭》)上面,點選一下。
此時,你會發現下方分欄裡面,內容也發生了變化。這個連結對應的原始碼被放在分欄區域正中,高亮顯示。
確認該區域就是我們要找的連結和文字描述後,我們滑鼠右鍵選擇高亮區域,並且在彈出的選單中,選擇 Copy -> Copy selector。
找一個文字編輯器,執行貼上,就可以看見我們究竟複製下來了什麼內容。
body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a
這一長串的標記,為電腦指出了:請你先找到 body 標記,進入它管轄的這個區域後去找 div.note
標記,然後找……最後找到 a 標記,這裡就是要找的內容了。
回到咱們的 Jupyter Notebook 中,用剛才獲得的標記路徑,定義變數sel。
sel = 'body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a'
我們讓 Python 從返回內容中,查詢 sel 對應的位置,把結果存到 results 變數中。
results = r.html.find(sel)
我們看看 results 裡面都有什麼。
results
這是結果:
[<Element 'a' href='https://www.jianshu.com/nb/130182' target='_blank'>]
results 是個列表,只包含一項。這一項包含一個網址,就是我們要找的第一個連結(《玉樹芝蘭》)對應的網址。
可是文字描述“《玉樹芝蘭》”哪裡去了?
彆著急,我們讓 Python 顯示 results 結果資料對應的文字。
results[0].text
這是輸出結果:
'玉樹芝蘭'
我們把連結也提取出來:
results[0].absolute_links
顯示的結果卻是一個集合。
{'https://www.jianshu.com/nb/130182'}
我們不想要集合,只想要其中的連結字串。所以我們先把它轉換成列表,然後從中提取第一項,即網址連結。
list(results[0].absolute_links)[0]
這次,終於獲得我們想要的結果了:
'https://www.jianshu.com/nb/130182'
有了處理這第一個連結的經驗,你信心大增,是吧?
其他連結,也無非是找到標記路徑,然後照貓畫虎嘛。
可是,如果每找一個連結,都需要手動輸入上面這若干條語句,那也太麻煩了。
這裡就是程式設計的技巧了。重複逐條執行的語句,如果工作順利,我們就要嘗試把它們歸併起來,做個簡單的函式。
對這個函式,只需給定一個選擇路徑(sel),它就把找到的所有描述文字和連結路徑都返回給我們。
def get_text_link_from_sel(sel):
mylist = []
try:
results = r.html.find(sel)
for result in results:
mytext = result.text
mylink = list(result.absolute_links)[0]
mylist.append((mytext, mylink))
return mylist
except:
return None
我們測試一下這個函式。
還是用剛才的標記路徑(sel)不變,試試看。
print(get_text_link_from_sel(sel))
輸出結果如下:
[('玉樹芝蘭', 'https://www.jianshu.com/nb/130182')]
沒問題,對吧?
好,我們試試看第二個連結。
我們還是用剛才的方法,使用下面分欄左上角的按鈕點選第二個連結。
下方出現的高亮內容就發生了變化:
我們還是用滑鼠右鍵點選高亮部分,拷貝出 selector。
然後我們直接把獲得的標記路徑寫到 Jupyter Notebook 裡面。
sel = 'body > div.note > div.post > div.article > div.show-content > div > p:nth-child(6) > a'
用我們剛才編制的函式,看看輸出結果是什麼?
print(get_text_link_from_sel(sel))
輸出如下:
[('如何用Python做詞雲?', 'https://www.jianshu.com/p/e4b24a734ccc')]
檢驗完畢,函式沒有問題。
下一步做什麼?
你還打算去找第三個連結,仿照剛才的方法做?
那你還不如全文手動摘取資訊算了,更省事兒一些。
我們要想辦法把這個過程自動化。
對比一下剛剛兩次我們找到的標記路徑:
body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a
以及:
body > div.note > div.post > div.article > div.show-content > div > p:nth-child(6) > a
發現什麼規律沒有?
對,路徑上其他的標記全都是一樣的,唯獨倒數第二個標記("p")後冒號後內容有區別。
這就是我們自動化的關鍵了。
上述兩個標記路徑裡面,因為指定了在第幾個“子”(nth-child
)文字段(paragraph,也就是"p"代表的含義)去找"a"這個標記,因此只返回來單一結果。
如果我們不限定"p"的具體位置資訊呢?
我們試試看,這次保留標記路徑裡面其他全部資訊,只修改"p"這一點。
sel = 'body > div.note > div.post > div.article > div.show-content > div > p > a'
再次執行我們的函式:
print(get_text_link_from_sel(sel))
這是輸出結果:
好了,我們要找的內容,全都在這兒了。
但是,我們的工作還沒完。
我們還得把採集到的資訊輸出到Excel中儲存起來。
還記得我們常用的資料框工具 Pandas 嗎?又該讓它大顯神通了。
import pandas as pd
只需要這一行命令,我們就能把剛才的列表變成資料框:
df = pd.DataFrame(get_text_link_from_sel(sel))
讓我們看看資料框內容:
df
內容沒問題,不過我們對錶頭不大滿意,得更換為更有意義的列名稱:
df.columns = ['text', 'link']
再看看資料框內容:
df
好了,下面就可以把抓取的內容輸出到Excel中了。
Pandas內建的命令,就可以把資料框變成csv格式,這種格式可以用Excel直接開啟檢視。
df.to_csv('output.csv', encoding='gbk', index=False)
注意這裡需要指定encoding(編碼)為gbk,否則預設的utf-8編碼在Excel中檢視的時候,有可能是亂碼。
我們看看最終生成的csv檔案吧。
很有成就感,是不是?
小結
本文為你展示了用Python自動網頁抓取的基礎技能。希望閱讀並動手實踐後,你能掌握以下知識點:
- 網頁抓取與網路爬蟲之間的聯絡與區別;
- 如何用 pipenv 快速構建指定的 Python 開發環境,自動安裝好依賴軟體包;
- 如何用 Google Chrome 的內建檢查功能,快速定位感興趣內容的標記路徑;
- 如何用 requests-html 包來解析網頁,查詢獲得需要的內容元素;
- 如何用 Pandas 資料框工具整理資料,並且輸出到 Excel。
或許,你覺得這篇文章過於淺白,不能滿足你的要求。
文中只展示瞭如何從一個網頁抓取資訊,可你要處理的網頁成千上萬啊。
彆著急。
本質上說,抓取一個網頁,和抓取10000個網頁,在流程上是一樣的。
而且,從咱們的例子裡,你是不是已經嘗試了抓取連結?
有了連結作為基礎,你就可以滾雪球,讓Python爬蟲“爬”到解析出來的連結上,做進一步的處理。
將來,你可能還要應對實踐場景中的一些棘手問題:
- 如何把抓取的功能擴充套件到某一範內內的所有網頁?
- 如何爬取Javascript動態網頁?
- 假設你爬取的網站對每個IP的訪問頻率做出限定,怎麼辦?
- ……
這些問題的解決辦法,我希望在今後的教程裡面,一一和你分享。
需要注意的是,網路爬蟲抓取資料,雖然功能強大,但學習與實踐起來有一定門檻。
當你面臨資料獲取任務時,應該先檢查一下這個清單:
- 有沒有別人已經整理好的資料集合可以直接下載?
- 網站有沒有對你需要的資料提供API訪問與獲取方式?
- 有沒有人針對你的需求,編好了定製爬蟲,供你直接呼叫?
如果答案是都沒有,才需要你自己編寫指令碼,調動爬蟲來抓取。
為了鞏固學習的知識,請你換一個其他網頁,以咱們的程式碼作為基礎修改後,抓取其中你感興趣的內容。
如果能把你抓取的過程記錄下來,在評論區將記錄連結分享給大家,就更好了。
因為刻意練習是掌握實踐技能的最好方式,而教是最好的學。
祝順利!
思考
本文主要內容講解完畢。
這裡給你提一個疑問,供你思考:
我們解析並且儲存的連結,其實是有重複的:
這並不是我們的程式碼有誤,而是在《如何用《玉樹芝蘭》入門資料科學?》一文裡,本來就多次引用過一些文章,所以重複的連結就都被抓取出來了
作者:王樹義
連結:https://www.jianshu.com/p/ba02079ecd2f
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。