網絡爬蟲之頁面解析

  • 2020 年 3 月 13 日
  • 筆記

作者:玩世不恭的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就該這樣使用

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屬性為lgdiv節點,此外,在上面的.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屬性值為lgdiv,然後繼續選取div節點下的直接子img節點(hidefoucus=true),最後選取其中的src屬性值
  • a_content_ls = bd_bj.xpath("//a//text()"),選取當前節點所有的a節點的所遇文本內容
  • a_news_content = bd_bj.xpath("//a[@class='mnav' and @name='tj_trnews']/text()"),多屬性選擇,在xpath中可以指定滿足多個屬性的節點,只需要and即可

Tips:讀者在閱讀的過程中注意將代碼和輸出的結果仔細對應起來,只要理解其中的意思在加上適當的訓練也就不難記憶了。

XPath小結

耐心看完了XPath的使用方法之後,聰明的讀者應該不難發現,其實Beautiful Soup和XPath的本質和思路上基本相同,只要我們在閱讀XPath用法的同時在腦袋中不斷的思考,相信聰明的你閱讀至此已經能夠基本掌握了XPath用法。

三、pyquery入門使用

對於pyquery,官方的解釋如下:

pyquery allows you to make jquery queries on xml documents. The API is as much as possible the similar to jquery. pyquery uses lxml for fast xml and html manipulation.
This is not (or at least not yet) a library to produce or interact with javascript code. I just liked the jquery API and I missed it in python so I told myself 「Hey let』s make jquery in python」. This is the result.
It can be used for many purposes, one idea that I might try in the future is to use it for templating with pure http templates that you modify using pyquery. I can also be used for web scrapping or for theming applications with Deliverance.
The project is being actively developped on a git repository on Github. I have the policy of giving push access to anyone who wants it and then to review what he does. So if you want to contribute just email me.
Please report bugs on the github issue tracker.

在網頁解析過程中,除了強大的Beautiful Soup和XPath之外,還有qyquery的存在,qyquery同樣受到了不少「蜘蛛er」的歡迎,下面我們來介紹下qyquery的使用。

節點選擇

與Beautiful Soup和XPath明顯不同的是,在qyquery中,一般存在着三種解析方式,一種是requests請求鏈接之後把html進行傳遞,一種是將url直接進行傳遞,還有一種是直接傳遞本地html文件路徑即可,讀者在實際使用的過程中根據自己的習慣來編碼即可,下面我們來看下這三種方式的表達:

import requests
from pyquery import PyQuery as pq

bd_html = requests.get("https://www.baidu.com").text
bd_url = "https://www.baidu.com"
bd_path = "./bd.html"

# 使用html參數進行傳遞
def way1(html):
    return pq(html)

# 使用url參數進行傳遞
def way2(url):
    return pq(url=url)

def way3(path):
    return pq(filename=path)

print(type(way1(html=bd_html)))
print(type(way2(url=bd_url)))
print(type(way3(path=bd_path)))

# <class 'pyquery.pyquery.PyQuery'>
# <class 'pyquery.pyquery.PyQuery'>
# <class 'pyquery.pyquery.PyQuery'>

從上面三種獲得解析對象方法的代碼中我們可以明顯看見都可以得到一樣的解析對象,接下來我們只要利用這個對象來對頁面進行解析從而提取出我們想要得到的有效信息即可,在qyquery中一般使用的是CSS選擇器來選取。下面我們仍然使用百度首頁來講解pyquery的使用,在這裡我們假設解析對象為bd_bj

response = requests.get("https://www.baidu.com")
response.encoding = "utf-8"

bd_bj = pq(response.text)

bd_title = bd_bj("title")
bd_img_ls = bd_bj("img")
bd_img_ls2 = bd_bj.find("img")
bd_mnav = bd_bj(".mnav")
bd_img = bd_bj("#u1 a")
bd_a_video = bd_bj("#u1 .mnav")

# <title>百度一下,你就知道</title>
# <img hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270" height="129"/> <img src="//www.baidu.com/img/gs.gif"/> 
# ......
# 輸出結果較長,讀者可自行運行

正如上面代碼所示,pyquery在進行節點提取的時候通常有三種方式,一種是直接提取出節點名即可提取出整個節點,當然這種方式你也可以使用find方法,這種提取節點的方式是不加任何屬性限定的,所以提取出的節點往往會含有多個,所以我們可以使用循環.items()來進行操作;一種是提取出含有特定class屬性的節點,這種形式採用的是.+class屬性值;還有一種是提取含有特定id屬性的節點,這種形式採用的是#+id屬性值。熟悉CSS的讀者應該不難理解以上提取節點的方法,正是在CSS中提取節點然後對其進行樣式操作的方法。上述三種方式您也可以像提取bd_a_video一樣混合使用

數據提取

在實際解析網頁的過程中,三種解析方式基本上大同小異,為了讀者認識pyquery的數據提取的操作以及博主日後的查閱,在這裡簡單的介紹下

img_src1 = bd_bj("img").attr("src"# //www.baidu.com/img/bd_logo1.png
img_src2 = bd_bj("img").attr.src    # //www.baidu.com/img/bd_logo1.png

for each in bd_bj.find("img").items():
    print(each.attr("src"))

print(bd_bj("title").text())    # 百度一下,你就知道

如上一二行代碼所示,提取節點屬性我們可以有兩種方式,這裡拿src屬性來進行說明,一種是.attr("src"),另外一種是.attr.src,讀者根據自己的習慣來操作即可,這裡需要注意的是:在節點提取小結中我們說了在不限制屬性的情況下是提取出所有滿足條件的節點,所以在這種情況下提取出的屬性是第一個節點屬性。要想提取所有的節點的屬性,我們可以如四五行代碼那樣使用.items()然後進行遍歷,最後和之前一樣提取各個節點屬性即可。qyquery提取節點中文本內容如第七行代碼那樣直接使用.text()即可。

pyquery小結

pyquery解析如Beautiful Soup和XPath思想一致,所以這了只是簡單的介紹了下,想要進一步了解的讀者可查閱官方文檔在加之熟練操作即可。

四、騰訊招聘網解析實戰

通過上述對Beautiful Soup、XPath以及pyquery的介紹,認真閱讀過的讀者想必已經有了一定的基礎,下面我們通過一個簡單的實戰案例來強化一下三種解析方式的操作。此次解析的網站為騰訊招聘網,網址url:https://hr.tencent.com/,其社會招聘網首頁如下所示:

此次我們的任務就是分別利用上述三種解析工具來接下該網站下的社會招聘中的所有數據。

網頁分析:

通過該網站的社會招聘的首頁,我們可以發現如下三條主要信息:

  • 首頁url連接為https://hr.tencent.com/position.php
  • 一共有288頁的數據,每頁10個職位,總職位共計2871
  • 數據字段有五個,分別為:職位名稱、職位類別、招聘人數、工作地點、職位發佈時間

既然我們解析的是該網站下所有職位數據,再者我們停留在第一頁也沒有發現其他有價值的信息,不如進入第二頁看看,這時我們可以發現網站的url鏈接有了一個比較明顯的變化,即原鏈接在用戶端提交了一個start參數,此時鏈接為https://hr.tencent.com/position.php?&start=10#a,陸續打開後面的頁面我們不難發現其規律:每一頁提交的start參數以10位公差進行逐步遞增。之後,我們使用谷歌開發者工具來審查該網頁,我們可以發現全站皆為靜態頁面,這位我們解析省下了不少麻煩,我們需要的數據就靜態的放置在table標籤內,如下所示:

下面我們具體來分別使用以上三種工具來解析該站所有職位數據。

案例源碼

import requests
from bs4 import BeautifulSoup
from lxml import etree
from pyquery import PyQuery as pq
import itertools
import pandas as pd

class TencentPosition():

    """
    功能: 定義初始變量
    參數:
        start: 起始數據
    """

    def __init__(self, start):
        self.url = "https://hr.tencent.com/position.php?&start={}#a".format(start)
        self.headers = {
            "Host""hr.tencent.com",
            "User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
        }
        self.file_path = "./TencentPosition.csv"

    """
    功能: 請求目標頁面
    參數:
        url: 目標鏈接
        headers: 請求頭
    返回:
        html,頁面源碼
    """

    def get_page(self, url, headers): 
        res = requests.get(url, headers=headers)
        try:
            if res.status_code == 200:
                return res.text
            else:
                return self.get_page(url, headers=headers)
        except RequestException as e:
            return self.get_page(url, headers=headers)

    """
    功能: Beautiful Soup解析頁面
    參數:
        html: 請求頁面源碼
    """

    def soup_analysis(self, html):
        soup = BeautifulSoup(html, "lxml")
        tr_list = soup.find("table", class_="tablelist").find_all("tr")
        for tr in tr_list[1:-1]:
            position_info = [td_data for td_data in tr.stripped_strings]
            self.settle_data(position_info=position_info)

    """
    功能: xpath解析頁面
    參數:
        html: 請求頁面源碼
    """

    def xpath_analysis(self, html):
        result = etree.HTML(html)
        tr_list = result.xpath("//table[@class='tablelist']//tr")
        for tr in tr_list[1:-1]:
            position_info = tr.xpath("./td//text()")
            self.settle_data(position_info=position_info)

    """
    功能: pyquery解析頁面
    參數:
        html: 請求頁面源碼
    """

    def pyquery_analysis(self, html):
        result = pq(html)
        tr_list = result.find(".tablelist").find("tr")
        for tr in itertools.islice(tr_list.items(), 111):
            position_info = [td.text() for td in tr.find("td").items()]
            self.settle_data(position_info=position_info)

    """
    功能: 職位數據整合
    參數:
        position_info: 字段數據列表
    """

    def settle_data(self, position_info):
        position_data = {
                "職位名稱": position_info[0].replace("xa0"" "),  # replace替換xa0字符防止轉碼error
                "職位類別": position_info[1],
                "招聘人數": position_info[2],
                "工作地點": position_info[3],
                "發佈時間": position_info[-1],
            }
        print(position_data)
        self.save_data(self.file_path, position_data)

    """
    功能: 數據保存
    參數:
        file_path: 文件保存路徑
        position_data: 職位數據
    """

    def save_data(self, file_path, position_data):
        df = pd.DataFrame([position_data])
        try:
            df.to_csv(file_path, header=False, index=False, mode="a+", encoding="gbk")  # 數據轉碼並換行存儲
        except:
            pass

if __name__ == "__main__":
    for page, index in enumerate(range(287)):
        print("正在爬取第{}頁的職位數據:".format(page+1))
        tp = TencentPosition(start=(index*10))
        tp_html = tp.get_page(url=tp.url, headers=tp.headers)
        tp.pyquery_analysis(html=tp_html)
        print("n")

部分結果如下:

總結

在本篇文章中,首先我們分別介紹了Beautiful Soup、XPath、pyquery的常見操作,之後通過使用該三種解析工具來爬取騰訊招聘網中所有的職位招聘數據,從而進一步讓讀者有一個更加深刻的認識。該案例中,由於本篇文章重點在於網站頁面的解析方法,所以未使用多線程、多進程,爬取所有的數據爬取的時間在一兩分鐘,在之後的文章中有時間的話會再次介紹多線程多進程的使用,案例中的解析方式都已介紹過,所以讀者閱讀源碼即可,如果對爬蟲頁面解析還有疑問的朋友歡迎在聯繫濤耶或者在下方留言,公眾號:【玩世不恭的Coder】,一個專註技術的分享區。

注意:本文章中所有的內容皆為在實際開發中常見的一些操作,並非所有,想要進一步提升技術能力的讀者務必請閱讀官方文檔。