網路爬蟲之頁面解析
作者:玩世不恭的Coder
時間:2020-03-13
說明:本文為原創文章,未經允許不可轉載,轉載前請聯絡濤耶
網路爬蟲之頁面解析
前言一、Beautiful Soup就該這樣使用節點選擇資料提取Beautiful Soup小結二、XPath解析頁面節點選擇資料提取XPath小結三、pyquery入門使用節點選擇資料提取pyquery小結四、騰訊招聘網解析實戰網頁分析:案例原始碼總結
前言
With the rapid development of the Internet,越來越多的資訊充斥著各大網路平臺。正如《死亡筆記》中L·Lawliet這一角色所提到的大數定律,在眾多繁雜的資料中必然存在著某種規律,偶然中必然包含著某種必然的發生。不管是我們提到的大數定律,還是最近火熱的大資料亦或其他領域都離不開大量而又幹淨資料的支援,為此,網路爬蟲能夠滿足我們的需求,即在網際網路上按照我們的意願來爬取我們任何想要得到的資訊,以便我們分析出其中的必然規律,進而做出正確的決策。同樣,在我們平時上網的過程中,無時無刻可見爬蟲的影子,比如我們廣為熟知的“度娘”就是其中一個大型而又名副其實的“蜘蛛王”(SPIDER KING)。而要想寫出一個強大的爬蟲程式,則離不開熟練的對各種網路頁面的解析,這篇文章將給讀者介紹如何在Python中使用各大主流的頁面解析工具。
常用的解析方式主要有正則、Beautiful Soup、XPath、pyquery,本文主要是講解後三種工具的使用,而對正則表示式的使用這裡不做講解,對正則有興趣瞭解的讀者可以跳轉:正則表示式
- Beautiful Soup的使用
- XPath的使用
- pyquery的使用
- Beautiful Soup、XPath、pyquery解析騰訊招聘網案例
一、Beautiful Soup就該這樣使用
Beautiful Soup是Python爬蟲中針對HTML、XML的其中一個解析工具,熟練的使用之可以很方便的提取頁面中我們想要的資料。此外,在Beautiful Soup中,為我們提供了以下四種解析器:
- 標準庫,
soup = BeautifulSoup(content, "html.parser")
- lxml解析器,
soup = BeautifulSoup(content, "lxml")
- xml解析器,
soup = BeautifulSoup(content, "xml")
- html5lib解析器,
soup = BeautifulSoup(content, "html5lib")
在以上四種解析庫中,lxml解析具有解析速度快相容錯能力強的merits,綜合考慮之下效能也較為不錯,所以本文主要使用的是lxml解析器,下面我們主要拿百度首頁的html來具體講解下Beautiful Soup應該如何使用,在此之前,我們且看一下這段小爬蟲:
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
response = requests.get("https://www.baidu.com")
encoding = response.apparent_encoding
response.encoding = encoding
print(BeautifulSoup(response.text, "lxml"))
程式碼解讀:
首先我們使用python中requests請求庫對百度首先進行get請求,然後獲取其編碼並將相應修改為對應的編碼格式以防止亂碼的可能性,最後通過BeautifluSoup對相應轉化為其能解析的形式,使用的解析器是lxml。
response = requests.get("https://www.baidu.com")
,requests請求百度連結encoding = response.apparent_encoding
,獲取頁面編碼格式response.encoding = encoding
,修改請求編碼為頁面對應的編碼格式,以避免亂碼print(BeautifulSoup(response.text, "lxml"))
,使用lxml解析器來對百度首頁html進行解析並列印結果
列印後的結果如下所示:
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta content="text/html;charset=utf-8" http-equiv="content-type"/><meta content="IE=Edge" http-equiv="X-UA-Compatible"/><meta content="always" name="referrer"/><link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/><title>百度一下,你就知道</title></head> <body link="#0000cc"> <div id="wrapper"> <div id="head"> <div class="head_wrapper"> <div class="s_form"> <div class="s_form_wrapper"> <div id="lg"> <img height="129" hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270"/>
# ... ...此處省略若干輸出
從上述程式碼中,我們可以看見打印出的內容過於雜亂無章,為了使得解析後的頁面與我們而言更加的清晰直觀,我們可以使用prettify()
方法來對其進行標準的縮排操作,為了方便講解,濤耶對結果進行適當的刪除,只留下有價值的內容,原始碼及輸出如下:
bd_soup = BeautifulSoup(response.text, "lxml")
print(bd_soup.prettify())
<html>
<head>
<title>
百度一下,你就知道
</title>
</head>
<body link="#0000cc">
<div id="wrapper">
<div id="head">
<div class="head_wrapper">
<div class="s_form">
<div class="s_form_wrapper">
<div id="lg">
<img height="129" hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270"/>
</div>
</div>
</div>
# ... ...此處省略若干輸出
</div>
</div>
</body>
</html>
節點選擇
在Beautiful Soup中,我們可以很方便的選擇想要得到的節點,只需要在bd_soup
物件中使用.
的方式即可獲得想要的物件,其內建api豐富,完全足夠我們實際的使用,其使用規則如下:
bd_title_bj = bd_soup.title # 獲取html中的title內容
bd_title_bj_name = bd_soup.title.name # .name獲取對應節點的名稱
bd_title_name = bd_soup.title.string
bd_title_parent_bj_name = bd_soup.title.parent.name # .parent獲取父節點
bd_image_bj = bd_soup.img # .img獲取img節點
bd_image_bj_dic = bd_soup.img.attrs # .attrs獲取屬性值
bd_image_all = bd_soup.find_all("img") # 通過find_all查詢對應的指定的所有節點
bd_image_idlg = bd_soup.find("div", id="lg") # 通過class屬性查詢節點
上述程式碼中解析的結果對應列印如下,大家對應輸出理解其意即可:
<title>百度一下,你就知道</title>
title
百度一下,你就知道
head
<img height="129" hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270"/>
{'hidefocus': 'true', 'src': '//www.baidu.com/img/bd_logo1.png', 'width': '270', 'height': '129'}
[<img height="129" hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270"/>, <img src="//www.baidu.com/img/gs.gif"/>]
<div id="lg"> <img height="129" hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270"/> </div>
程式碼解讀:
bd_soup.title
,正如前面所說,Beautiful Soup可以很簡單的解析對應的頁面,只需要使用bd_soup.
的方式進行選擇節點即可,該行程式碼正是獲得百度首頁html的title
節點內容bd_soup.title.name
,使用.name
的形式即可獲取節點的名稱bd_soup.title.string
,使用.string
的形式即可獲得節點當中的內容,這句程式碼就是獲取百度首頁的title節點的內容,即瀏覽器導航條中所顯示的百度一下,你就知道
bd_soup.title.parent.name
,使用.parent
可以該節點的父節點,通俗地講就是該節點所對應的上一層節點,然後使用.name
獲取父節點名稱bd_soup.img
,如bd_soup.title
一樣,該程式碼獲取的是img
節點,只不過需要注意的是:在上面html中我們可以看見總共有兩個img
節點,而如果使用.img
的話預設是獲取html中的第一個img
節點,而不是所有bd_soup.img.attrs
,獲取img
節點中所有的屬性及屬性內容,該程式碼輸出的結果是一個鍵值對的字典格式,所以之後我們只需要通過字典的操作來獲取屬性所對應的內容即可。比如bd_soup.img.attrs.get("src")
和bd_soup.img.attrs["src"]
的方式來獲取img
節點所對應的src
屬性的內容,即圖片連結bd_soup.find_all("img")
,在上述中的.img
操作預設只能獲取第一個img
節點,而要想獲取html中所有的img
節點,我們需要使用.find_all("img")
方法,所返回的是一個列表格式,列表內容為所有的選擇的節點bd_soup.find("div", id="lg")
,在實際運用中,我們往往會選擇指定的節點,這個時候我們可以使用.find()
方法,裡面可傳入所需查詢節點的屬性,這裡需要注意的是:在傳入class
屬性的時候其中的寫法是.find("div", class_="XXX")
的方式。所以該行程式碼表示的是獲取id
屬性為lg
的div
節點,此外,在上面的.find_all()
同樣可以使用該方法來獲取指定屬性所對應的所有節點
資料提取
在上一小節節點選擇我們講到了部分資料提取的方法,然而,Beautiful Soup的強大之處還不止步於此。接下來我們繼續揭開其神祕的面紗。(注意:以下只是進行常用的一些api,如若有更高的需求可檢視官方文件)
- .get_text()
獲取物件中所有的文字內容(即我們在頁面中肉眼可見的文字):
all_content = bd_soup.get_text()
百度一下,你就知道 新聞 hao123 地圖 視訊 貼吧 登入 document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登入</a>');
更多產品 關於百度 About Baidu ©2017 Baidu 使用百度前必讀 意見反饋 京ICP證030173號
- .strings,.stripped_strings
print(type(bd_soup.strings))
# <class 'generator'>
.strings
用於提取bd_soup
物件中所有的內容,而從上面的輸出結果我們可以看出.strings
的型別是一個生成器,對此可以使用迴圈來提取出其中的內容。但是我們在使用.strings
的過程中會發現提取出來的內容有很多的空格以及換行,對此我們可以使用.stripped_strings
方法來解決該問題,用法如下:
for each in bd_soup.stripped_strings:
print(each)
輸出結果:
百度一下,你就知道
新聞
hao123
地圖
視訊
貼吧
登入
更多產品
關於百度
About Baidu
©2017 Baidu
使用百度前必讀
意見反饋
京ICP證030173號
- .parent,.children,.parents
.parent
可以選擇該節點的父節點,.children
可以選擇該節點的孩子節點,.parents
選擇該節點所有的上層節點,返回的是生成器型別,各用法如下:
bd_div_bj = bd_soup.find("div", id="u1")
print(type(bd_div_bj.parent))
print("*" * 50)
for child in bd_div_bj.children:
print(child)
print("*" * 50)
for parent in bd_div_bj.parents:
print(parent.name)
結果輸出:
<class 'bs4.element.Tag'>
**************************************************
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新聞</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地圖</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">視訊</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">貼吧</a>
**************************************************
div
div
div
body
html
Beautiful Soup小結
Beautiful Soup主要的用法就是以上一些,還有其他一些操作在實際開發過程中使用的並不是很多,這裡不做過多的講解了,所以整體來講Beautiful Soup的使用還是比較簡單的,其他一些操作可見官方文件:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#contents-children
二、XPath解析頁面
XPath全稱是XML Path Language,它既可以用來解析XML,也可以用來解析HTML。在上一部分已經講解了Beautiful Soup的一些常見的騷操作,在這裡,我們繼續來看看XPath的使用,瞧一瞧XPath的功能到底有多麼的強大以致於受到了不少開發者的青睞。同Beautiful Soup一樣,在XPath中提供了非常簡潔的節點選擇的方法,Beautiful Soup主要是通過.
的方式來進行子節點或者子孫節點的選擇,而在XPath中則主要通過/
的方式來選擇節點。除此之外,在XPath中還提供了大量的內建函式來處理各個資料之間的匹配關係。
首先,我們先來看看XPath常見的節點匹配規則:
表示式 | 解釋說明 |
---|---|
/ |
在當前節點中選取直接子節點 |
// |
在當前節點中選取子孫節點 |
. |
選取當前節點 |
.. |
選取當前節點的父節點 |
@ |
指定屬性(id、class……) |
下面我們繼續拿上面的百度首頁的HTML來講解下XPath的使用。
節點選擇
要想正常使用Xpath,我們首先需要正確匯入對應的模組,在此我們一般使用的是lxml
,操作示例如下:
from lxml import etree
import requests
import html
if __name__ == "__main__":
response = requests.get("https://www.baidu.com")
encoding = response.apparent_encoding
response.encoding = encoding
print(response.text)
bd_bj = etree.HTML(response.text)
bd_html = etree.tostring(bd_bj).decode("utf-8")
print(html.unescape(bd_html))
1~9行程式碼如Beautiful Soup一致,下面對之後的程式碼進行解釋:
etree.HTML(response.text)
,使用etree
模組中的HTML
類來對百度html(response.text)
進行初始化以構造XPath解析物件,返回的型別為etree.tostring(bd_html_elem).decode("utf-8")
,將上述的物件轉化為字串型別且編碼為utf-8
html.unescape(bd_html)
,使用HTML5標準定義的規則將bd_html
轉換成對應的unicode字元。
打印出的結果如Beautiful Soup使用時一致,這裡就不再顯示了,不知道的讀者可回翻。既然我們已經得到了Xpath可解析的物件(bd_bj)
,下面我們就需要針對這個物件來選擇節點了,在上面我們也已經提到了,XPath主要是通過/
的方式來提取節點,請看下面Xpath中節點選擇的一些常見操作:
all_bj = bd_bj.xpath("//*") # 選取所有節點
img_bj = bd_bj.xpath("//img") # 選取指定名稱的節點
p_a_zj_bj = bd_bj.xpath("//p/a") # 選取直接節點
p_a_all_bj = bd_bj.xpath("//p//a") # 選取所有節點
head_bj = bd_bj.xpath("//title/..") # 選取父節點
結果如下:
[<Element html at 0x14d6a6d1c88>, <Element head at 0x14d6a6e4408>, <Element meta at 0x14d6a6e4448>, <Element meta at 0x14d6a6e4488>, <Element meta at 0x14d6a6e44c8>, <Element link at 0x14d6a6e4548>, <Element title at 0x14d6a6e4588>, <Element body at 0x14d6a6e45c8>, <Element div at 0x14d6a6e4608>, <Element div at 0x14d6a6e4508>, <Element div at 0x14d6a6e4648>, <Element div at 0x14d6a6e4688>, ......]
[<Element img at 0x14d6a6e4748>, <Element img at 0x14d6a6e4ec8>]
[<Element a at 0x14d6a6e4d88>, <Element a at 0x14d6a6e4dc8>, <Element a at 0x14d6a6e4e48>, <Element a at 0x14d6a6e4e88>]
[<Element a at 0x14d6a6e4d88>, <Element a at 0x14d6a6e4dc8>, <Element a at 0x14d6a6e4e48>, <Element a at 0x14d6a6e4e88>]
[<Element head at 0x14d6a6e4408>]
all_bj = bd_bj.xpath("//*")
,使用//
可以選擇當前節點(html)
下的所有子孫節點,且以一個列表的形式來返回,列表元素通過bd_bj
一樣是element
物件,下面的返回型別一致img_bj = bd_bj.xpath("//img")
,選取當前節點下指定名稱的節點,這裡建議與Beautiful Soup的使用相比較可增強記憶,Beautiful Soup是通過.find_all("img")
的形式p_a_zj_bj = bd_bj.xpath("//p/a")
,選取當前節點下的所有p
節點下的直接子a
節點,這裡需要注意的是”直接“,如果a
不是p
節點的直接子節點則選取失敗p_a_all_bj = bd_bj.xpath("//p//a")
,選取當前節點下的所有p
節點下的所有子孫a
節點,這裡需要注意的是”所有“,注意與上一個操作進行區分head_bj = bd_bj.xpath("//title/..")
,選取當前節點下的title
節點的父節點,即head
節點
資料提取
在瞭解如何選擇指定的節點之後,我們就需要提取節點中所包含的資料了,具體提取請看下面的示例:
img_href_ls = bd_bj.xpath("//img/@src")
img_href = bd_bj.xpath("//div[@id='lg']/img[@hidefocus='true']/@src")
a_content_ls = bd_bj.xpath("//a//text()")
a_news_content = bd_bj.xpath("//a[@class='mnav' and @name='tj_trnews']/text()")
輸出結果:
['//www.baidu.com/img/bd_logo1.png', '//www.baidu.com/img/gs.gif']
['//www.baidu.com/img/bd_logo1.png']
['新聞', 'hao123', '地圖', '視訊', '貼吧', '登入', '更多產品', '關於百度', 'About Baidu', '使用百度前必讀', '意見反饋']
['新聞']
img_href_ls = bd_bj.xpath("//img/@src")
,該程式碼先選取了當前節點下的所有img
節點,然後將所有img
節點的src
屬性值選取出來,返回的是一個列表形式img_href = bd_bj.xpath("//div[@id='lg']/img[@hidefocus='true']/@src")
,該程式碼首先選取了當前節點下所有id
屬性值為lg
的div
,然後繼