1. 程式人生 > >python 獲取網頁的內容

python 獲取網頁的內容

1.安裝pip

我的個人桌面系統用的linuxmint,系統預設沒有安裝pip,考慮到後面安裝requests模組使用pip,所以我這裡第一步先安裝pip。

 

1

$ sudo apt install python-pip

安裝成功,檢視PIP版本:

 

 

1

$ pip -V

2.安裝requests模組

這裡我是通過pip方式進行安裝:

 

1

$ pip install requests

 

執行import requests,如果沒提示錯誤,那說明已經安裝成功了!

 

檢驗是否安裝成功

3.安裝beautifulsoup4

Beautiful Soup 是一個可以從HTML或XML檔案中提取資料的Python庫。它能夠通過你喜歡的轉換器實現慣用的文件導航,查詢、修改文件的方式。Beautiful Soup會幫你節省數小時甚至數天的工作時間。

 

1

$ sudo apt-get install python3-bs4

注:這裡我使用的是python3的安裝方式,如果你用的是python2,可以使用下面命令安裝。

 

1

$ sudo pip install beautifulsoup4

4.requests模組淺析

1)傳送請求

首先當然是要匯入 Requests 模組:

 

1

>>> import requests

然後,獲取目標抓取網頁。這裡我以下為例:

 

1

>>> r = requests.get('http://www.jb51.net/article/124421.htm'

)

這裡返回一個名為 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

>>> payload = {'newwindow': '1', 'q': 'python爬蟲', 'oq': 'python爬蟲'}

 

>>> r = requests.get("https://www.google.com/search", params=payload)

3)響應內容

通過r.text或r.content來獲取頁面響應內容。

 

1

2

3

4

5

>>> import requests

 

>>> r = requests.get('https://github.com/timeline.json')

 

>>> r.text

Requests 會自動解碼來自伺服器的內容。大多數 unicode 字符集都能被無縫地解碼。這裡補充一點r.text和r.content二者的區別,簡單說:

resp.text返回的是Unicode型的資料;

resp.content返回的是bytes型也就是二進位制的資料;

所以如果你想取文字,可以通過r.text,如果想取圖片,檔案,則可以通過r.content。

4)獲取網頁編碼

 

1

2

3

4

5

>>> r = requests.get('http://www.cnblogs.com/')

 

>>> r.encoding

 

'utf-8'

5)獲取響應狀態碼

我們可以檢測響應狀態碼:

 

1

2

3

4

5

>>> r = requests.get('http://www.cnblogs.com/')

 

>>> r.status_code

 

200

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

#!/usr/bin/env python

# -*- coding: utf-8 -*-

_author_ = 'GavinHsueh'

 

import requests

import bs4

 

#要抓取的目標頁碼地址

url = 'http://www.ranzhi.org/book/ranzhi/about-ranzhi-4.html'

 

#抓取頁碼內容,返回響應物件

response = requests.get(url)

 

#檢視響應狀態碼

status_code = response.status_code

 

#使用BeautifulSoup解析程式碼,並鎖定頁碼指定標籤內容

content = bs4.BeautifulSoup(response.content.decode("utf-8"), "lxml")

element = content.find_all(id='book')

 

print(status_code)

print(element)

 

一下為獲取網頁上特定的內容的程式   沒有註釋  

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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。