網路爬蟲大型教程(二)

程式設計師duke發表於2018-05-14

網路爬蟲(二)

一 基礎爬取

1. 獲取網頁內容

urllib是Python的標準庫,包含了從網路請求資料,處理cookie,甚至改變像請求頭和使用者代理這些後設資料的函式

from urllib.request import urlopen
html = urlopen("http://pythonscraping.com/pages/page1.html")
print(html.read())

2. Beautifulsoup

通過定位 HTML 標籤來格式化和組織複雜的網路資訊,用簡單易用的 Python 物件為我們展現 XML 結構資訊。

安裝初步常用包


$pip install bs4
$pip install requests
$pip install lxml

3.第一個例子

from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page1.html")
bsObj = BeautifulSoup(html.read(),"html.parser")
print(bsObj.h1)

匯入 urlopen,然後呼叫 html.read() 獲取網頁的 HTML 內容。這樣就可以把 HTML 內容傳到 BeautifulSoup 物件,轉換成下面的結構 :

 html → <html><head>...</head><body>...</body></html>
    — head → <head><title>A Useful Page<title></head>
        — title → <title>A Useful Page</title>
    — body → <body><h1>An Int...</h1><div>Lorem ip...</div></body>
        — h1 → <h1>An Interesting Title</h1>
        — div → <div>Lorem Ipsum dolor...</div>

從網頁中提取的 h1 標籤被嵌在 BeautifulSoup 物件 bsObj 結構的第二層

(html → body → h1)。不過從物件裡提取 h1 標籤的時候,可以直接呼叫它:

bsObj.h1

下面這些函式呼叫都可以達到同樣的效果。

bsObj.html.body.h1
bsObj.body.h1
bsObj.html.h1

4.異常處理

try:
    html = urlopen("http://www.pythonscraping.com/pages/page1.html")
except HTTPError as e:
    print(e)
    # 返回空值,中斷程式,或者執行另一個方案
else:
    # 程式繼續。注意:如果你已經在上面異常捕捉那一段程式碼裡返回或中斷(break),
    # 那麼就不需要使用else語句了,這段程式碼也不會執行

當自己呼叫標籤不存在,也會導致錯誤的發生。因此,也要對呼叫標籤出現異常時進行處理。因此對上述兩種異常進行綜合處理:

try:
    badContent = bsObj.nonExistingTag.anotherTag
except AttributeError as e:
    print("Tag was not found")
else:
    if badContent == None:
        print ("Tag was not found")
    else:
        print(badContent)

用異常處理方法對上個例子進行重寫:

from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
    try:
        html = urlopen(url)
    except HTTPError as e:
        return None
    try:
        bsObj = BeautifulSoup(html.read(),"html.parser")
        title = bsObj.body.h1
    except AttributeError as e:
        return None
    return title
title = getTitle("http://www.pythonscraping.com/pages/page1.html")
if title == None:
    print("Title could not be found")
else:
    print(title)

5. 通過css來抓取所需結果

抓取css中所有指定的標籤

from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bsObj = BeautifulSoup(html)
nameList = bsObj.findAll("span", {"class":"green"})
for name in nameList:
    print(name.get_text())

在這裡使用了.get_text()獲取標籤內的文字內容,那什麼時候用get_text()?什麼時候保留標籤呢?

​ .get_text() 會把你正在處理的 HTML 文件中所有的標籤都清除,然後返回一個只包含文字的字串。 假如你正在處理一個包含許多超連結、段落和標籤的大段原始碼, 那麼 .get_text() 會把這些超連結、段落和標籤都清除掉,只剩下一串不帶標籤的文字。

​ 用 BeautifulSoup 物件查詢你想要的資訊,比直接在 HTML 文字里查詢資訊要簡單得多。 通常在你準備列印、儲存和運算元據時,應該最後才使用 .get_text()。

一般情況下,你應該儘可能地保留 HTML 文件的標籤結構。

6. BeautifulSoup的find()和findAll()

定義:

find(tag, attributes, recursive, text, keywords)

findAll(tag, attributes, recursive, text, limit, keywords)

引數的含義:

標籤引數 tag —可以傳一個標籤的名稱或多個標籤名稱組成的 Python列表做標籤引數。

.findAll({“h1”,”h2”,”h3”,”h4”,”h5”,”h6”})

屬性引數 attributes—用一個 Python 字典封裝一個標籤的若干屬性和對應的屬性值。

.findAll(“span”, {“class”:{“green”, “red”}})

遞迴引數 recursive 是一個布林變數 。

預設值是true,會去查詢標籤的子標籤,如果設為false,只會查一級標籤。

文字引數 text 有點不同,它是用標籤的文字內容去匹配,而不是用標籤的屬性。

nameList = bsObj.findAll(text="the prince")
print(len(nameList)) 

範圍限制引數 limit -只用於findAll方法。find其實就是findAll的limit等於1時的情形。

關鍵詞引數 keyword,可以讓你選擇那些具有指定屬性的標籤。

任何用關鍵詞引數能夠完成的任務,同樣可以用其他技術解決。

7. 處理層級標籤

給定一個指定的頁面結構如下所示:

 html
    — body
        — div.wrapper
            — h1
            — div.content
            — table#giftList
            — tr
                — th
                — th
                — th
                — th
            — tr.gift#gift1
                — td
                — td
                    — span.excitingNote
                — td
                — td
                    — img
            — ……其他表格行省略了……
        — div.footer

處理子標籤和其後代標籤

from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
#子標籤處理
def getChild(url):
    try:
        html = urlopen(url)
    except HTTPError as e:
        return None
    try:
        bsobj = BeautifulSoup(html,"html.parser")
    except AttributeError as e:
        return None
    return bsobj
bs_obj = getChild("http://www.pythonscraping.com/pages/page3.html")
for child in bs_obj.find("table",{"id":"giftList"}).children:
    print(child)

處理兄弟標籤:

def getSibling(url):
    try:
        html = urlopen(url)
    except HTTPError as e:
        print(e)
        return None
    try:
        bsObj = BeautifulSoup(html,"html.parser")
    except AttributeError as e:
        print(e)
        return None
    return bsObj
bs_obj = getSibling("http://www.pythonscraping.com/pages/page3.html")
for sibling in bs_obj.find("table",{"id":"giftList"}).tr.next_siblings:
    print(sibling)

​ 如果我們選擇 bsObj.table.tr 或直接就用 bsObj.tr 來獲取表格中的第一行,上面的程式碼也可以獲得正確的結果。 但是,我們還是採用更長的形式寫了一行程式碼,這可以避免各種意外:bsObj.find(“table”,{“id”:”giftList”}).tr
即使頁面上只有一個表格(或其他目標標籤),只用標籤也很容易丟失細節。另外,頁面佈局總是不斷變化的。一個標籤這次是在表格中第一行的位置,沒準兒哪天就在第二行或第三行了。 如果想讓你的爬蟲更穩定,最好還是讓標籤的選擇更加具體。如果有屬性,就利用標籤的屬性。

處理父標籤:

在爬蟲抓取網頁時,查詢父標籤的次數很少,但是還需要了解一下。

def getParent(url):
    try:
        html = urlopen(url)
    except HTTPError as e:
        print(e)
        return None
    try:
        bs_obj = BeautifulSoup(html,"html.parser")
    except AttributeError as e:
        print(e)
        return None
    return bs_obj
bsObj = getParent("http://www.pythonscraping.com/pages/page3.html")
print(bsObj.find("img",{"src":"../img/gifts/img1.jpg"}).parent.previous_sibling.get_text())

8.正規表示式和BeautifulSoup

注意觀察網頁上有幾個商品圖片——它們的原始碼形式如下:

img src=”../img/gifts/img3.jpg”

用正規表示式對其進行擷取查詢

from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
import re

def getImage(url):
    try:
        html = urlopen(url)
    except HTTPError as e:
        print(e)
        return None
    try:
        bs_obj = BeautifulSoup(html,"html.parser")
    except AttributeError as e:
        print(e)
        return None
    return bs_obj

bsObj = getImage("http://www.pythonscraping.com/pages/page3.html")
patten = re.compile(r'\.\./img/gifts/img.*\.jpg')
images = bsObj.findAll("img",{"src":re.compile(patten)})
for image in images:
    print(image["src"])

9. 獲取屬性

myTag.attrs

要注意這行程式碼返回的是一個 Python 字典物件,可以獲取和操作這些屬性。比如要獲取圖片的資源位置 src,可以用下面這行程式碼:
myImgTag.attrs[“src”]

10. Lambda表示式

Lambda 表示式本質上就是一個函式, 可以作為其他函式的變數使用;也就是說,一個函
數不是定義成 f(x, y),而是定義成 f(g(x), y),或 f(g(x), h(x)) 的形式。

#下面的程式碼就是獲取有兩個屬性的標籤:
soup.findAll(lambda tag: len(tag.attrs) == 2)
#這行程式碼會找出下面的標籤:
<div class="body" id="content"></div>
<span style="color:red" class="title"></span>

11.其他庫

lxml
這個庫可以用來解析 HTML 和 XML 文件,以非常底層的實現而聞名於世,大部分原始碼是用 C 語言寫的,在處理絕大多數 HTML 文件時速度都非常快。

相關文章