爬虫教程( 1 ) --- 初级、基础、实践
爬蟲教程:https://piaosanlang.gitbooks.io/spiders/content/
如何入門 Python 爬蟲:https://zhuanlan.zhihu.com/p/21479334
靜覓 崔慶才的個人博客 Python 爬蟲系列:http://cuiqingcai.com/category/technique/python
http://www.cnblogs.com/miqi1992/category/1105419.html
Python 爬蟲從入門到放棄系列博客:https://www.cnblogs.com/zhaof/tag/爬蟲/default.html?page=2
Python 爬取功能匯總:https://www.jb51.net/Special/985.htm
Python 3.8.5 文檔
官方文檔:https://docs.python.org/zh-cn/3/
1. 爬蟲入門 初級篇
IDE 選擇: PyCharm (推薦) 、SublimeText3、Visual Studio
Python 版本:Python3。( 最簡單的是直接安裝 Anaconda,使用 Anaconda 管理虛擬環境?)
- Windows 平臺:https://docs.python.org/zh-cn/3/using/windows.html
- Linux Ubuntu 平臺:https://docs.python.org/zh-cn/3/using/unix.html
1.1 為什么要學習爬蟲
學習需求:抓取的某個網站或者某個應用的內容,提取有用的價值
實現手段:模擬用戶在瀏覽器或者應用(app)上的操作,實現自動化的程序爬蟲應用場景(利用爬蟲能做什么?)
大家最熟悉的應用場景:搶票神器(360搶票器)、投票神器(微信朋友圈投票)
企業應用場景
- 拉勾網招聘職位數據分析報告
- 2016年中國外賣O2O行業發展報告
-
2016年中國在線出境游市場研究報告
1、各種熱門公司招聘中的職位數及月薪分布
2、對某個 App 的下載量跟蹤
3、 飲食地圖
4、 票房預測
1.2 爬蟲是什么 ?
專業術語: 網絡爬蟲(又被稱為網頁蜘蛛,網絡機器人)是一種按照一定的規則,自動的抓取萬維網信息的程序或者腳本。
爬蟲起源(產生背景):隨著網絡的迅速發展,萬維網成為大量信息的載體,如何有效地提取并利用這些信息成為一個巨大的挑戰;搜索引擎有Yahoo,Google,百度等,作為一個輔助人們檢索信息的工具成為用戶訪問萬維網的入口和指南。網絡爬蟲是搜索引擎系統中十分重要的組成部分,它負責從互聯網中搜集網頁,采集信息,這些網頁信息用于建立索引從而為搜索 引擎提供支持,它決定著整個引擎系統的內容是否豐富,信息是否即時,因此其性能的優劣直接影響著搜索引擎的效果。
網絡爬蟲程序的優劣,很大程度上反映了一個搜索引擎的好差。不信,你可以隨便拿一個網站去查詢一下各家搜索對它的網頁收錄情況,爬蟲強大程度跟搜索引擎好壞基本成正比。
搜索引擎工作原理
- 第一步:抓取網頁(爬蟲)。搜索引擎是通過一種特定規律的軟件跟蹤網頁的鏈接,從一個鏈接爬到另外一個鏈接,像蜘蛛在蜘蛛網上爬行一樣,所以被稱為“蜘蛛”也被稱為“機器人”。搜索引擎蜘蛛的爬行是被輸入了一定的規則的,它需要遵從一些命令或文件的內容。 ? ? ? Robots協議(也稱為爬蟲協議、機器人協議等)的全稱是“網絡爬蟲排除標準”(Robots Exclusion Protocol),網站通過Robots協議告訴搜索引擎哪些頁面可以抓取,哪些頁面不能抓取。
robots.txt?示例:??https://www.taobao.com/robots.txt? ? ? ??http://www.qq.com/robots.txt
示例:CSDN robot.txt ( https://blog.csdn.net/robots.txt ) 文件中 Sitemap: # -*- coding: UTF-8 -*-import re import requestsdef download(url, retry_count=3):html = Nonefor retry in range(retry_count):print(f'Downloading : {url}')custom_headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'}try:r = requests.get(url, headers=custom_headers, verify=False)if r.status_code == 200:print('status_code : {0}'.format(r.status_code))html = r.text # html = r.contentelse:print('status_code : {0}'.format(r.status_code))breakexcept BaseException as ex:print(f'Download error : {ex}')return htmlif __name__ == "__main__":temp_url = r'https://blog.csdn.net/s/sitemap/pcsitemapindex.xml'sitemap = download(temp_url)links = re.findall(r'<loc>(.*?)</loc>', sitemap)for link in links:print(link)pass - 第二步:數據存儲。搜索引擎是通過蜘蛛跟蹤鏈接爬行到網頁,并將爬行的數據存入原始頁面數據庫。其中的頁面數據與用戶瀏覽器得到的 HTML 是完全一樣的。搜索引擎蜘蛛在抓取頁面時,也做一定的重復內容檢測,一旦遇到權重很低的網站上有大量抄襲、采集或者復制的內容,很可能就不再爬行。
- 第三步:預處理。搜索引擎將蜘蛛抓取回來的頁面,進行各種步驟的預處理。? ?⒈提取文字,?⒉中文分詞, ⒊去停止詞, ⒋消除噪音(搜索引擎需要識別并消除這些噪聲,比如版權聲明文字、導航條、廣告等……), 5 正向索引,? 6 倒排索引, 7 鏈接關系計算, 8 特殊文件處理等。?除了HTML文件外,搜索引擎通常還能抓取和索引以文字為基礎的多種文件類型,如 PDF、Word、WPS、XLS、PPT、TXT 文件等。我們在搜索結果中也經常會看到這些文件類型。但搜索引擎還不能處理圖片、視頻、Flash 這類非文字內容,也不能執行腳本和程序。
- 第四步:排名,提供檢索服務
但是,這些通用性搜索引擎也存在著一定的局限性,如:
- (1) 不同領域、不同背景的用戶往往具有不同的檢索目的和需求,通用搜索引擎所返回的結果包含大量用戶不關心的網頁。
- (2) 通用搜索引目標是盡可能的網絡覆蓋率,有限的搜索引擎服務器資源與無限的網絡數據資源之間的矛盾將進一步加深。
- (3) 萬維網數據形式的豐富和網絡技術的不斷發展,圖片、數據庫、音頻、視頻多媒體等不同數據大量出現,通用搜索引擎往往對這些信息含量密集且具有一定結構的數據無能為力,不能很好地發現和獲取。
- (4) 通用搜索引擎大多提供基于關鍵字的檢索,難以支持根據語義信息提出的查詢。
為了解決上述問題,定向抓取相關網頁資源的聚焦爬蟲應運而生。 聚焦爬蟲是一個自動下載網頁的程序,它根據既定的抓取目標,有選擇的訪問萬維網上的網頁與相關的鏈接,獲取所需要的信息。
與通用爬蟲(general purpose web crawler)不同,聚焦爬蟲并不追求大的覆蓋,而將目標定為抓取與某一特定主題內容相關的網頁,為面向主題的用戶查詢準備數據資源。
聚焦爬蟲工作原理以及關鍵技術概述:
網絡爬蟲是一個自動提取網頁的程序,它為搜索引擎從萬維網上下載網頁,是搜索引擎的重要組成。傳統爬蟲從一個或若干初始網頁的URL開始,獲得初始網頁上的URL,在抓取網頁的過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統的一定停止條件。聚焦爬蟲的工作流程較為復雜,需要根據一定的網頁分析算法過濾與主題無關的鏈接,保留有用的鏈接并將其放入等待抓取的URL隊列。然后,它將根據一定的搜索策略從隊列中選擇下一步要抓取的網頁URL,并重復上述過程,直到達到系統的某一條件時停止。另外,所有被爬蟲抓取的網頁將會被系統存貯,進行一定的分析、過濾,并建立索引,以便之后的查詢和檢索;對于聚焦爬蟲來說,這一過程所得到的分析結果還可能對以后的抓取過程給出反饋和指導。
相對于通用網絡爬蟲,聚焦爬蟲還需要解決三個主要問題:
- (1) 對抓取目標的描述或定義;
- (2) 對網頁或數據的分析與過濾;
- (3) 對URL的搜索策略。
抓取目標的描述和定義是決定網頁分析算法與URL搜索策略如何制訂的基礎。而網頁分析算法和候選URL排序算法是決定搜索引擎所提供的服務形式和爬蟲網頁抓取行為的關鍵所在。這兩個部分的算法又是緊密相關的。
網絡爬蟲的發展趨勢
隨著 AJAX/Web2.0 的流行,如何抓取 AJAX 等動態頁面成了搜索引擎急需解決的問題,如果搜索引擎依舊采用“爬”的機制,是無法抓取到 AJAX 頁面的有效數據的。 對于 AJAX 這樣的技術,所需要的爬蟲引擎必須是基于驅動的。而如果想要實現事件驅動,首先需要解決以下問題:
- 第一:JavaScript 的交互分析和解釋;
- 第二:DOM 事件的處理和解釋分發;
- 第三:動態 DOM 內容語義的抽取。
爬蟲發展的幾個階段(博士論文copy)
- 第一個階段:可以說是 早期爬蟲,斯坦福的幾位同學完成的抓取,當時的互聯網基本都是完全開放的,人類流量是主流;
- 第二個階段:是?分布式爬蟲,但是爬蟲面對新的問題是數據量越來越大,傳統爬蟲已經解決不了把數據都抓全的問題,需要更多的爬蟲,于是調度問題就出現了;
- 第三階段:是?Deep Web?爬蟲。此時面對新的問題是數據之間的link越來越少,比如淘寶,點評這類數據,彼此link很少,那么抓全這些數據就很難;還有一些數據是需要提交查詢詞才能獲取,比如機票查詢,那么需要尋找一些手段“發現”更多,更完整的不是明面上的數據。
- 第四階段:智能爬蟲,這主要是爬蟲又開始面對新的問題:社交網絡數據的抓取。
社交網絡對爬蟲帶來的新的挑戰包括
- 有一條賬號護城河。我們通常稱UGC(User Generated Content)指用戶原創內容。為 web2.0,即數據從單向傳達,到雙向互動,人民群眾可以與網站產生交互,因此產生了賬號,每個人都通過賬號來標識身份,提交數據,這樣一來社交網絡就可以通過封賬號來提高數據抓取的難度,通過賬號來發現非人類流量。之前沒有賬號只能通過cookie和ip。cookie又是易變,易揮發的,很難長期標識一個用戶。
- 網絡走向封閉。新浪微博在 2012 年以前都是基本不封的,隨便寫一個程序怎么抓都不封,但是很快,越來越多的站點都開始防止競爭對手,防止爬蟲來抓取,數據逐漸走向封閉,越來越多的人難以獲得數據。甚至都出現了專業的爬蟲公司,這在2010年以前是不可想象的。。
- 反爬手段,封殺手法千差萬別。寫一個通用的框架抓取成百上千萬的網站已經成為歷史,或者說已經是一個技術相對成熟的工作,也就是已經有相對成熟的框架來”盜“成百上千的墓,但是極個別的墓則需要特殊手段了,目前市場上比較難以抓取的數據包括,微信公共賬號,微博,facebook,ins,淘寶等等。具體原因各異,但基本無法用一個統一框架來完成,太特殊了。如果有一個通用的框架能解決我說的這幾個網站的抓取,這一定是一個非常震撼的產品,如果有,一定要告訴我,那我公開出來,然后就改行了。
當面對以上三個挑戰的時候,就需要智能爬蟲。智能爬蟲是讓爬蟲的行為盡可能模仿人類行為,讓反爬策略失效,只有”混在老百姓隊伍里面,才是安全的“,因此這就需要琢磨瀏覽器了,很多人把爬蟲寫在了瀏覽器插件里面,把爬蟲寫在了手機里面,寫在了路由器里面(春節搶票王)。再有一個傳統的爬蟲都是只有讀操作的,沒有寫操作,這個很容易被判是爬蟲,智能的爬蟲需要有一些自動化交互的行為,這都是一些抵御反爬策略的方法。
1.3 爬蟲基本原理
爬蟲是模擬用戶在瀏覽器或者某個應用上的操作,把操作的過程、實現自動化的程序,當我們在瀏覽器中輸入一個 url 后回車,后臺會發生什么?比如說你輸入http://www.sina.com.cn/,簡單來說這段過程發生了以下四個步驟:
- 1. 查找域名對應的IP地址。
- 2. 向IP對應的服務器發送請求。
- 3. 服務器響應請求,發回網頁內容。
- 4. 瀏覽器解析網頁內容。
網絡爬蟲本質:本質就是瀏覽器http請求。瀏覽器和網絡爬蟲是兩種不同的網絡客戶端,都以相同的方式來獲取網頁。
網絡爬蟲要做的,簡單來說,就是實現瀏覽器的功能。通過指定url,直接返回給用戶所需要的數據, 而不需要一步步人工去操縱瀏覽器獲取。
瀏覽器是如何發送和接收這個數據呢?
- HTTP 簡介:HTTP協議(HyperText Transfer Protocol,超文本傳輸協議)目的是為了提供一種發布和接收HTML(HyperText Markup Language)頁面的方法。
- HTTP 協議所在的協議層(了解):HTTP 是基于TCP協議之上的。在 TCP/IP 協議參考模型的各層對應的協議如下圖,其中HTTP是應用層的協議。 默認HTTP的端口號為80,HTTPS的端口號為443。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 網絡模型圖
HTTP 工作過程
一次 HTTP 操作稱為一個事務,其工作整個過程如下:
- 1 ) 、地址解析。如用客戶端瀏覽器請求這個頁面:http://localhost.com:8080/index.htm從中分解出協議名、主機名、端口、對象路徑等部分,對于我們的這個地址,解析得到的結果如下: 協議名:http 主機名:localhost.com 端口:8080 對象路徑:/index.htm在這一步,需要域名系統DNS解析域名localhost.com,得主機的IP地址。
- 2)、封裝 HTTP 請求數據包。把以上部分結合本機自己的信息,封裝成一個HTTP請求數據包
- 3)封裝成 TCP 包,建立TCP連接(TCP的三次握手)。在HTTP工作開始之前,客戶機(Web瀏覽器)首先要通過網絡與服務器建立連接,該連接是通過TCP來完成的,該協議與IP協議共同構建Internet,即著名的TCP/IP協議族,因此Internet又被稱作是TCP/IP網絡。HTTP是比TCP更高層次的應用層協議,根據規則,只有低層協議建立之后才能,才能進行更層協議的連接,因此,首先要建立TCP連接,一般TCP連接的端口號是80。這里是8080端口
- 4)客戶機發送請求命令。建立連接后,客戶機發送一個請求給服務器,請求方式的格式為:統一資源標識符(URL)、協議版本號,后邊是MIME信息包括請求修飾符、客戶機信息和可內容。
- 5)服務器響應。服務器接到請求后給予相應的響應信息,其格式為一個狀態行,包括信息的協議版本號、一個成功或錯誤的代碼,后邊是 MIME 信息包括服務器信息、實體信息和可能的內容。實體消息:服務器向瀏覽器發送頭信息后,它會發送一個空白行來表示頭信息的發送到此為結束,接著,它就以Content-Type應答頭信息所描述的格式發送用戶所請求的實際數據
- 6)服務器關閉 TCP 連接。一般情況下,一旦 Web 服務器向瀏覽器發送了請求數據,它就要關閉 TCP 連接,然后如果瀏覽器或者服務器在其頭信息加入了這行代碼 Connection:keep-alive 。TCP連接在發送后將仍然保持打開狀態,于是,瀏覽器可以繼續通過相同的連接發送請求。保持連接節省了為每個請求建立新連接所需的時間,還節約了網絡帶寬。
HTTP 協議棧數據流
HTTPS。HTTPS(全稱:Hypertext Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL。其所用的端口號是443。
SSL:安全套接層,是netscape公司設計的主要用于web的安全傳輸協議。這種協議在WEB上獲得了廣泛的應用。通過證書認證來確??蛻舳撕途W站服務器之間的通信數據是加密安全的。
有兩種基本的加解密算法類型:
- 1)對稱加密(symmetrcic encryption):密鑰只有一個,加密解密為同一個密碼,且加解密速度快,典型的對稱加密算法有DES、AES,RC5,3DES等;對稱加密主要問題是共享秘鑰,除你的計算機(客戶端)知道另外一臺計算機(服務器)的私鑰秘鑰,否則無法對通信流進行加密解密。解決這個問題的方案非對稱秘鑰。
- 2)非對稱加密:使用兩個秘鑰:公共秘鑰和私有秘鑰。私有秘鑰由一方密碼保存(一般是服務器保存),另一方任何人都可以獲得公共秘鑰。這種密鑰成對出現(且根據公鑰無法推知私鑰,根據私鑰也無法推知公鑰),加密解密使用不同密鑰(公鑰加密需要私鑰解密,私鑰加密需要公鑰解密),相對對稱加密速度較慢,典型的非對稱加密算法有RSA、DSA等。
https 通信的優點:
客戶端產生的密鑰只有客戶端和服務器端能得到;
加密的數據只有客戶端和服務器端才能得到明文;
客戶端到服務端的通信是安全的。
1.4 爬蟲工作流程
網絡爬蟲的基本工作流程如下:
- 1. 首先選取一部分精心挑選的種子 URL;
- 2. 將這些 URL 放入待抓取 URL 隊列;
- 3. 從待抓取 URL 隊列中取出待抓取在 URL,解析 DNS,并且得到主機的 ip,將 URL 對應的網頁下載下來并存儲到已下載網頁庫中。此外,將這些 URL 放進已抓取 URL 隊列。
- 4. 分析已抓取 URL 隊列中的 URL,分析其中的其他 URL,并且將 URL 放入待抓取 URL 隊列,從而進入下一個循環。
?;ňW 爬取 示例:爬取 大學?;??http://www.521609.com/daxuexiaohua/ )
這個爬蟲只是爬取一個 URL,并沒有提取更多 URL 進行爬取
# -*- coding:utf-8 -*- import os import chardet import requests from bs4 import BeautifulSoupdef spider():url = "http://www.521609.com/daxuexiaohua/"proxies = {"http": "http://172.17.18.80:8080","https": "https://172.17.18.80:8080"}r = requests.get(url,# proxies=proxies)html = r.content.decode("gbk")soup = BeautifulSoup(html, "lxml")divs = soup.find_all("div", class_="index_img list_center")print(f'len(divs) : {len(divs)}')for div in divs:tag_ul = div.find('ul')tag_all_li = tag_ul.find_all('li')print(f'len(tag_all_li): {len(tag_all_li)}')for tag_li in tag_all_li:tag_img = tag_li.find('img')print(f'mm_name: {tag_img["alt"]}')print(f'\t\t mm_pic: http://www.521609.com{tag_img["src"]}')home_page = tag_li.find('a')print(f'\t\t home_page: http://www.521609.com{home_page["href"]}')# print(soup)if __name__ == "__main__":spider()# input('press any key to continue......')# pass1.5 HTTP 代理神器 Fidder
Fiddler 不但能截獲各種瀏覽器發出的HTTP請求, 也可以截獲各種智能手機發出的 HTTP/HTTPS請求。 Fiddler 能捕獲 IOS 設備發出的請求,比如 IPhone, IPad, MacBook. 等等蘋果的設備。 同理,也可以截獲 Andriod,Windows Phone 的等設備發出的HTTP/HTTPS。工作原理:Fiddler 是以代理 web 服務器的形式工作的,它使用代理地址:127.0.0.1,端口:8888。
Fiddler 抓取 HTTPS 設置:啟動 Fiddler,打開菜單欄中的 Tools > Fiddler Options,打開 “Fiddler Options” 對話框。
選中 Capture HTTPS CONNECTs,再選中下方 Ignore server certificate errors,
然后再點擊 Actions 安裝證書( 要抓取 HTTPS 的流量,必須安裝證書 ),安裝為 "根證書"。。。
配置 Fiddler 允許遠程連接( 可以抓取手機流量?):
?Connections 頁簽,選中 Allow remote computers to connect。重啟 Fidler(這一步很重要,必須做)。
Fiddler 如何捕獲 Chrome的會話:switchyomega 安裝插件?
百度( 百度的時候把點去掉?):i點sha點dow點socks? 或者? lan點tern
打開網址 chrome 網上應用商店,然后搜索 "switchyomega"。
Fiddler 如何捕獲 Firefox 的會話
能支持 HTTP 代理的任意程序的數據包都能被 Fiddler 嗅探到,Fiddler 的運行機制其實就是本機上監聽 8888 端口的 HTTP代理。 Fiddler2啟動的時候默認IE的代理設為了127.0.0.1:8888,而其他瀏覽器是需要手動設置的,所以將Firefox的代理改為127.0.0.1:8888就可以監聽數據了。 Firefox 上通過如下步驟設置代理 點擊: Tools -> Options, 在Options 對話框上點擊Advanced tab - > network tab -> setting.
Fiddler 的基本界面
特別注意: 遇到這個 Click 請點擊 Click?
Fiddler 強大的 Script 系統
Fiddler 包含了一個強大的基于事件腳本的子系統,并且能使用 .net 語言進行擴展。?
官方的幫助文檔:?http://www.fiddler2.com/Fiddler/dev/ScriptSamples.asp
Fiddler 的?Fiddler Script 標簽,如下圖:
在里面我們就可以編寫腳本了, 看個實例讓所有 cnblogs 的會話都顯示紅色。 把這段腳本放在 OnBeforeRequest(oSession: Session) 方法下,并且點擊 "Save script"
if (oSession.HostnameIs("www.cnblogs.com")) {oSession["ui-color"] = "red"; }這樣所有的cnblogs的會話都會顯示紅色。
1.6 HTTP 協議介紹
HTTP (HyperText Transfer Protocol) 提供了一種發布和接收HTML(HyperText Markup Language)頁面的方法。
Http 兩部分組成:請求、響應。
客戶端請求消息:客戶端發送一個HTTP請求到服務器的請求消息包括以下格式:請求行(request line)、請求頭部(header)、空行和請求數據四個部分組成,下圖給出了請求報文的一般格式。
服務器響應消息:HTTP 響應也由四個部分組成,分別是:狀態行、消息報頭、空行 和 響應正文。
cookies 和 session
服務器 和 客戶端 的交互僅限于請求/響應過程,結束之后便斷開, 在下一次請求服務器會認為新的客戶端。為了維護他們之間的鏈接,讓服務器知道這是前一個用戶發送的請求,必須在一個地方保存客戶端的信息:
- Cookie 通過在客戶端記錄信息確定用戶身份。
- Session 通過在服務器端記錄信息確定用戶身份。
HTTP 請求
請求方法
根據 HTTP 標準,HTTP 請求可以使用多種請求方法。
HTTP 1.0 定義了三種請求方法: GET,POST 和 HEAD方法。
HTTP 1.1 新增了五種請求方法:OPTIONS,PUT,DELETE,TRACE 和 CONNECT 方法。
| 序號 | 方法 | 描述 |
| 1 | GET | 請求指定的頁面信息,并返回實體主體。 |
| 2 | HEAD | 類似于 get 請求,只不過返回的響應中沒有具體的內容,用于獲取報頭 |
| 3 | POST | 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。 POST 請求可能會導致新的資源的建立和/或已有資源的修改。 |
| 4 | PUT | 從客戶端向服務器傳送的數據取代指定的文檔的內容。 |
| 5 | DELETE | 請求服務器刪除指定的頁面。 |
| 6 | CONNECT | HTTP/1.1 協議中預留給能夠將連接改為管道方式的代理服務器。 |
| 7 | OPTIONS | 允許客戶端查看服務器的性能。 |
| 8 | TRACE | 回顯服務器收到的請求,主要用于測試或診斷。 |
GET和POST方法區別歸納如下幾點:
- 1. GET 是從服務器上獲取數據,POST 是向服務器傳送數據。
- 2. GET 請求的時候,參數都顯示在瀏覽器網址上。POST 請求的時候,參數在請求體當中,消息長度沒有限制而且以隱式的方式進行發送
- 3. 盡量避免使用 Get 方式提交表單,因為有可能會導致安全問題。 比如說在登陸表單中用 Get 方式,用戶輸入的用戶名和密碼將在地址欄中暴露無遺。 但是在分頁程序中用 Get 方式就比用 Post 好。
URL概述
統一資源定位符(URL,英語 Uniform / Universal Resource Locator的縮寫)是用于完整地描述 Internet 上網頁和其他資源的地址的一種標識方法。
URL 格式:基本格式如下 schema://host[:port#]/path/…/[?query-string][#anchor]
- 1. schema 協議 (例如:http, https, ftp)
- 2. host 服務器的IP地址或者域名
- 3. port# 服務器的端口(如果是走協議默認端口,缺省端口80)
- 4. path 訪問資源的路徑
- 5. query-string 參數,發送給http服務器的數據
- 6. anchor- 錨(跳轉到網頁的指定錨點位置)
例子:
URL 和 URI 的區別
- URL:統一資源定位符(uniform resource location);平時上網時在 IE 瀏覽器中輸入的那個地址就是 URL。比如:網易 http://www.163.com 就是一個URL 。URL 是 Internet上用來描述信息資源的字符串,主要用在各種 WWW 客戶程序和服務器程序上。采用 URL 可以用一種統一的格式來描述各種信息資源,包括文件、服務器的地址和目錄等。
- URI:統一資源標識符(uniform resource identifier)。Web 上可用的每種資源 - HTML 文檔、圖像、視頻片段、程序, 由一個通過通用資源標志符 (Universal Resource Identifier, 簡稱 "URI") 進行定位。URI 是個純粹的語法結構,用于指定標識 web資源的字符串的各個不同部分,URL 是 URI 的一個特例,它包含定位 web 資源的足夠信息。
- URL 是 URI 的一個子集
一個 URL 的請求過程:
- 當你在瀏覽器輸入URL?https://www.baidu.com/ 的時候,瀏覽器發送一個Request 去獲取?https://www.baidu.com/ 的 html。
- 服務器把 Response 發送回給瀏覽器.
- 瀏覽器分析 Response 中的 HTML,發現其中引用了很多其他文件,比如:圖片,CSS文件,JS文件。
- 瀏覽器會自動再次發送 Request 去獲取圖片,CSS文件,或者JS文件。
- 當所有的文件都下載成功后, 網頁就被顯示出來了。
常用的請求報頭
- Host:Host初始URL中的主機和端口,用于指定被請求資源的Internet主機和端口號,它通常從HTTP URL中提取出來的
- Connection:表示客戶端與服務連接類型;
1. client 發起一個包含 Connection:keep-alive 的請求
2. server 收到請求后,如果 server 支持 keepalive 回復一個包含Connection:keep-alive的
? ?響應不關閉連接,否則回復一個包含Connection:close的響應關閉連接。
3. 如果 client 收到包含 Connection:keep-alive 的響應,向同一個連接發送下一個請求,
? ?直到一方主動關閉連接。 Keep-alive在很多情況下能夠重用連接,減少資源消耗,縮短響應時間HTTP - Accept:表示瀏覽器支持的 MIME 類型
MIME 的英文全稱是 Multipurpose Internet Mail Extensions(多用途互聯網郵件擴展)
eg:
Accept:image/gif,表明客戶端希望接受GIF圖象格式的資源;
Accept:text/html,表明客戶端希望接受html文本。
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
意思:瀏覽器支持的 MIME 類型分別是 text/html、application/xhtml+xml、application/xml 和 */*,
? ? ? 優先順序是它們從左到右的排列順序。
Text:用于標準化地表示的文本信息,文本消息可以是多種字符集和或者多種格式的;
Application:用于傳輸應用程序數據或者二進制數據;設定某種擴展名的文件用一種應用程序來
? ? ? ? ? ? ?打開的方式類型,當該擴展名文件被訪問的時候,瀏覽器會自動使用指定應用程序來打開
q 是權重系數,范圍 0 =< q <= 1,q 值越大,請求越傾向于獲得其“;”之前的類型表示的內容,
若沒有指定 q 值越大,請求越傾向于獲得其“,則默認為1,
若被賦值為0,則用于提醒服務器哪些是瀏覽器不接受的內容類型。
| Mime類型 | 擴展名 |
| text/html | .htm?.html *.shtml |
| text/plain | text/html是以html的形式輸出,比如<input type="text"/>就會在頁面上顯示一個文本框,而以plain形式就會在頁面上原樣顯示這段代碼 |
| application/xhtml+xml | .xhtml?.xml |
| text/css | *.css |
| application/msexcel | .xls?.xla |
| application/msword | .doc?.dot |
| application/octet-stream | *.exe |
| application/pdf | |
| ..... | ..... |
- Content-Type:POST 提交,application/x-www-form-urlencoded 提交的數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。
- User-Agent: 瀏覽器類型
- Referer: 請求來自哪個頁面,用戶是從該 Referer URL頁面訪問當前請求的頁面。
- Accept-Encoding:瀏覽器支持的壓縮編碼類型,比如gzip,支持gzip的瀏覽器返回經gzip編碼的HTML頁面。許多情形下這可以減少5到10倍的下載時間
eg:
Accept-Encoding:gzip;q=1.0, identity; q=0.5, *;q=0 // 按順序支持 gzip , identity
如果有多個Encoding同時匹配, 按照q值順序排列
如果請求消息中沒有設置這個域,服務器假定客戶端對各種內容編碼都可以接受。
- Accept-Language:瀏覽器所希望的語言種類,當服務器能夠提供一種以上的語言版本時要用到。
eg:
Accept-Language:zh-cn
如果請求消息中沒有設置這個報頭域,服務器假定客戶端對各種語言都可以接受。 - Accept-Charset:瀏覽器可接受的字符集,用于指定客戶端接受的字符集
eg:
Accept-Charset:iso-8859-1,gb2312
ISO8859-1,通常叫做Latin-1。Latin-1包括了書寫所有西方歐洲語言不可缺少的附加字符;
gb2312是標準中文字符集;
UTF-8 是 UNICODE 的一種變長字符編碼,可以解決多種語言文本顯示問題,從而實現應用國際化和本地化。
如果在請求消息中沒有設置這個域,缺省是任何字符集都可以接受。
HTTP 響應
服務器上每一個HTTP 應答對象 response 都包含一個數字 "狀態碼"。HTTP 狀態碼表示 HTTP 協議所返回的響應的狀態。
比如:客戶端向服務器發送請求,如果成功地獲得請求的資源,則返回的狀態碼為200,表示響應成功。如果請求的資源不存在, 則通常返回404錯誤。
HTTP?響應狀態碼通常分為5種類型,分別以1~5五個數字開頭,由3位整數組成,第一個數字定義了響應的類別:
| 分類 | 分類描述 |
| 1** | 信息,服務器收到請求,需要請求者繼續執行操作 |
| 2** | 成功,操作被成功接收并處理 |
| 3** | 重定向,需要進一步的操作以完成請求 |
| 4** | 客戶端錯誤,請求包含語法錯誤或無法完成請求 |
| 5** | 服務器錯誤,服務器在處理請求的過程中發生了錯誤 |
最常用的響應狀態碼
200 (OK): 請求成功,找到了該資源,并且一切正常。處理方式:獲得響應的內容,進行處理?
201 請求完成,結果是創建了新資源。新創建資源的URI可在響應的實體中得到 ? ?處理方式:爬蟲中不會遇到?
202 請求被接受,但處理尚未完成 ? ?處理方式:阻塞等待?
204 服務器端已經實現了請求,但是沒有返回新的信 息。如果客戶是用戶代理,則無須為此更新自身的文檔視圖。 ? ?處理方式:丟棄
300 該狀態碼不被HTTP/1.0的應用程序直接使用, 只是作為3XX類型回應的默認解釋。存在多個可用的被請求資源。 ? ?處理方式:若程序中能夠處理,則進行進一步處理,如果程序中不能處理,則丟棄
301 (Moved Permanently): 請求的文檔在其他地方,新的URL在Location頭中給出,瀏覽器應該自動地訪問新的URL。處理方式:重定向到分配的URL
302 (Found): 類似于301,但新的URL應該被視為臨時性的替代,而不是永久性的。處理方式:重定向到臨時的URL?
304 (NOT MODIFIED): 該資源在上次請求之后沒有任何修改。這通常用于瀏覽器的緩存機制。處理方式:丟棄?
400 (Bad Request): 請求出現語法錯誤。非法請求 ? ? 處理方式:丟棄?
401 未授權 ? ? 處理方式:丟棄?
403 (FORBIDDEN): 客戶端未能獲得授權。這通常是在401之后輸入了不正確的用戶名或密碼。禁止訪問 ? ?處理方式:丟棄?
404 (NOT FOUND): 在指定的位置不存在所申請的資源。沒有找到 。 ? ?處理方式:丟棄?
5XX 回應代碼以“5”開頭的狀態碼表示服務器端發現自己出現錯誤,不能繼續執行請求 ? ?處理方式:丟棄
500 (Internal Server Error): 服務器遇到了意料不到的情況,不能完成客戶的請求
503 (Service Unavailable): 服務器由于維護或者負載過重未能應答。
例如,Servlet可能在數據庫連接池已滿的情況下返回503。服務器返回503時可以提供一個Retry-After頭
常用的響應報頭(了解)
Location:表示客戶應當到哪里去提取文檔,用于重定向接受者到一個新的位置
Server:服務器名字,包含了服務器用來處理請求的軟件信息
eg: Server響應報頭域的一個例子:
Server:Apache-Coyote/1.1
Set-Cookie:設置和頁面關聯的Cookie。
例如:前一個 cookie 被存入瀏覽器并且瀏覽器試圖請求 http://www.ibm.com/foo/index.html 時
Set-Cookie:customer=huangxp; path=/foo; domain=.ibm.com;?
expires= Wednesday, 19-OCT-05 23:12:40 GMT;
Set-Cookie的每個屬性解釋如下:
Customer=huangxp 一個"名稱=值"對,把名稱customer設置為值"huangxp",這個屬性在Cookie中必須有。
path=/foo 服務器路徑。
domain=.ibm.com 指定cookie 的域名。
expires= Wednesday, 19-OCT-05 23:12:40 GMT 指定cookie 失效的時間
POST 請求方式
urllib 使用示例:
# encoding:UTF-8 import urllib.requestdef get_data():url = "http://www.baidu.com"data = urllib.request.urlopen(url).read()z_data = data.decode('UTF-8')print(z_data)get_data()get 和 post 方法:
import urllib.request import urllib.parse import urllib.error # url異常處理def get_method():# GET 方法keyword = "python"keyword = urllib.parse.quote(keyword) # 搜索中文的方法url = "http://www.baidu.com/s?wd=" + keywordreq = urllib.request.Request(url)data = urllib.request.urlopen(req).read()print(data.decode('utf-8', 'ignore')) # 轉為utf-8,如果出現異常,責忽略fh = open("./1.html", "wb")fh.write(data)fh.close()def post_method():# POST 方法keyword = "python"keyword = urllib.parse.quote(keyword)url = "http://www.baidu.com/s"my_data = urllib.parse.urlencode({"wd": "python"}).encode('utf-8')req = urllib.request.Request(url, my_data)data = urllib.request.urlopen(req).read().decode("utf-8")print(data)if __name__ == '__main__':get_method()post_method()'''在清求url請求時會發現存在異常情況,常用的請求異常類是 HTTPError/URLError 兩種,HTTPError異常時URLError異常的子類,是帶異常狀態碼和異常原因的,而URLError異常類是不帶狀態碼的,所以在使用中不用直接用URLError代替HTTPError異常,如果要代替,一定要判斷是否有狀態碼屬性URLError:1/連接不上遠程服務器;2/url不存在;3/本地沒有網絡;4/假如觸發 http error所以通常用特殊處理來使用URLError來替代HTTPError異常'''try:urllib.request.urlopen("http://www.blog.csdn.net")except urllib.error.URLError as e:if hasattr(e, "code"):print("code:")print(e.code)if hasattr(e, "reason"):print("reason:")print(e.reason)finally:print("URLError ")pass抓取拉鉤招聘信息:https://www.lagou.com/jobs/list_爬蟲
# -*- coding: utf-8 -*-import string from urllib import request, parse# proxy_handler = request.ProxyHandler({"http": 'http://192.168.17.1:8888'}) # opener = request.build_opener(proxy_handler) # request.install_opener(opener)page_num = 1 output = open('lagou.json', 'w')for page in range(1, page_num + 1):form_data = {'first': 'true','pn': '1','kd': '爬蟲'}# 注意:對于需要傳入的 data 數據,需要進行 urlencode 編碼。form_data = parse.urlencode(form_data).encode('utf-8')print(f'運行到第 ({page}) 頁面')send_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36','Accept': 'application/json, text/javascript, */*; q=0.01','X-Requested-With': 'XMLHttpRequest'}# url = 'http://www.lagou.com/jobs/positionAjax.json?px=new&needAddtionalResult=false'url = 'https://www.lagou.com/jobs/positionAjax.json?city=北京&needAddtionalResult=false'# Python3 出現 'ascii' codec can't encode characters問題# https://www.cnblogs.com/wuxiangli/p/9957601.htmlurl = parse.quote(url, safe=string.printable)req = request.Request(url=url, headers=send_headers, data=form_data, method='POST')# req.add_header('X-Requested-With', 'XMLHttpRequest')# req.headers = send_headersresponse = request.urlopen(req)res_html = response.read().decode('utf-8')print(res_html)# output.write(res_html + '\n') output.close() print('-' * 4 + 'end' + '-' * 4)提出一個問題,如果要采集的是 拉鉤招聘網站 北京>>朝陽區>>望京 以這個網站為例,該如何理解這個url ?
http://www.lagou.com/jobs/list_?px=default&city=%E5%8C%97%E4%BA%AC&district=%E6%9C%9D%E9%98%B3%E5%8C%BA&bizArea=%E6%9C%9B%E4%BA%AC#filterBox
URL 編碼 / 解碼:http://tool.chinaz.com/tools/urlencode.aspx
# -*- coding: utf-8 -*-from urllib import request, parsequery = {'city': '北京','district': '朝陽區','bizArea': '望京' } print(parse.urlencode(query)) page = 3 values = {'first': 'false','pn': str(page),'kd': '后端開發', } form_data = parse.urlencode(values) print(form_data)小結
- Content-Length: 是指報頭Header以外的內容長度,指 表單數據長度
- X-Requested-With: XMLHttpRequest :表示Ajax異步請求
- Content-Type: application/x-www-form-urlencoded :表示:提交的表單數據 會按照name/value 值對 形式進行編碼。例如:name1=value1&name2=value2... 。name 和 value 都進行了 URL 編碼(utf-8、gb2312)
2. 爬蟲入門 基礎篇
| 數據格式 | 描述 | 設計目標 |
| XML | Extensible Markup Language (可擴展標記語言) | 被設計為傳輸和存儲數據,其焦點是數據的內容 |
| HTML | HyperText Markup Language(超文本標記語言) | 顯示數據以及如何更好顯示數據 |
| HTML DOM | HTML Document Object Model(文檔對象模型) | 通過 JavaScript,您可以重構整個HTML文檔。您可以添加、移除、改變或重排頁面上的項目。要改變頁面的某個東西,JavaScript就需要對HTML文檔中所有元素進行訪問的入口。 |
XML
XML DOM 定義訪問和操作 XML 文檔的標準方法。 DOM 將 XML 文檔作為一個樹形結構,而樹葉被定義為節點。
XML 示例
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book category="cooking"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> <price>30.00</price> </book> <book category="children"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> <book category="web"> <title lang="en">XQuery Kick Start</title> <author>James McGovern</author> <author>Per Bothner</author> <author>Kurt Cagle</author> <author>James Linn</author> <author>Vaidyanathan Nagarajan</author> <year>2003</year> <price>49.99</price> </book> <book category="web" cover="paperback"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> </bookstore>HTML DOM 示例
HTML DOM 定義了訪問和操作 HTML 文檔的標準方法。 DOM 以樹結構表達 HTML 文檔。
2.1 頁面解析之數據提取
抓取網站或者某個應用的數據一般分為兩部分,非結構化的文本,或結構化的文本。
- 結構化的數據:JSON、XML
- 非結構化的數據:HTML文本(包含JavaScript代碼)
HTML文本(包含JavaScript代碼)是最常見的數據格式,理應屬于結構化的文本組織,但因為一般我們需要的關鍵信息并非直接可以得到,需要進行對HTML的解析查找,甚至一些字符串操作才能得到,所以還是歸類于非結構化的數據處理中。
把網頁比作一個人,那么 HTML 便是他的骨架,JS 便是他的肌肉,CSS 便是它的衣服。常見解析方式如下:
- XPath
- CSS選擇器
- 正則表達式
一段文本,例如一篇文章,或者一句話,我們的初衷是提取有效信息,所以如果是滯后處理,可以直接存儲,如果是需要實時提取有用信息,常見的處理方式如下:
- 分詞,根據抓取的網站類型,使用不同詞庫,進行基本的分詞,然后變成詞頻統計
- NLP 自然語言處理,進行語義分析,用結果表示,例如正負面等。
2.2 非結構化數據之 XPath
XPath 語言:XPath(XML Path Language)是 XML 路徑語言,它是一種用來定位 XML 文檔中某部分位置的語言。將 HTML 轉換成 XML 文檔之后,用 XPath 查找 HTML 節點或元素。比如:用 "/" 來作為上下層級間的分隔,第一個 "/" 表示文檔的根節點(注意,不是指文檔最外層的 tag 節點,而是指文檔本身),對于一個 HTML 文件來說,最外層的節點應該是 "/html" 。
XPath開發工具:開源的 XPath 表達式編輯工具:XMLQuire(XML格式文件可用)、chrome 插件 XPath Helper
firefox插件 XPath Checker
XPath語法 (?XPath語法參考文檔:http://www.w3school.com.cn/xpath/index.asp )
XPath 是一門在 XML 文檔中查找信息的語言。
XPath 可用來在 XML 文檔中對元素和屬性進行遍歷。
選取節點 XPath 使用路徑表達式在 XML 文檔中選取節點。節點是通過沿著路徑或者 step 來選取的。
下面列出了最有用的路徑表達式:
| 表達式 | 描述 |
| / | 從根節點選取。 |
| nodename | 選取此節點的所有子節點。 |
| // | 從當前節點 選擇?所有匹配文檔中的節點 |
| . | 選取當前節點。 |
| .. | 選取當前節點的父節點。 |
| @ | 選取屬性。 |
在下面的表格中,我們已列出了一些路徑表達式以及表達式的結果:
| 路徑表達式 | 結果 |
| /bookstore | 選取根元素 bookstore。注釋:假如路徑起始于正斜杠( / ),則此路徑始終代表到某元素的絕對路徑! |
| bookstore | 選取 bookstore 元素的所有子節點。默認從根節點選取 |
| bookstore/book | 選取屬于 bookstore 的子元素的所有 book 元素。 |
| //book | 選取所有 book 子元素,而不管它們在文檔中的位置。 |
| //book/./title | 選取所有 book 子元素,從當前節點查找title節點 |
| //price/.. | 選取所有 book 子元素,從當前節點查找父節點 |
| bookstore//book | 選擇屬于 bookstore 元素的后代的所有 book 元素,而不管它們位于 bookstore 之下的什么位置。 |
| //@lang | 選取名為 lang 的所有屬性。 |
謂語條件(Predicates)
謂語用來查找某個特定的信息或者包含某個指定的值的節點。
所謂"謂語條件",就是對路徑表達式的附加條件
謂語是被嵌在方括號中,都寫在方括號"[]"中,表示對節點進行進一步的篩選。
在下面的表格中,我們列出了帶有謂語的一些路徑表達式,以及表達式的結果:
| 路徑表達式 | 結果 |
| /bookstore/book[1] | 選取屬于 bookstore 子元素的第一個 book 元素。 |
| /bookstore/book[last()] | 選取屬于 bookstore 子元素的最后一個 book 元素。 |
| /bookstore/book[last()-1] | 選取屬于 bookstore 子元素的倒數第二個 book 元素。 |
| /bookstore/book[position()<3] | 選取最前面的兩個屬于 bookstore 元素的子元素的 book 元素。 |
| //title[@lang] | 選取所有擁有名為 lang 的屬性的 title 元素。 |
| //title[@lang=’eng’] | 選取所有 title 元素,且這些元素擁有值為 eng 的 lang 屬性。 |
| //book[price] | 選取所有 book 元素,且被選中的book元素必須帶有price子元素 |
| /bookstore/book[price>35.00] | 選取 bookstore 元素的所有 book 元素,且其中的 price 元素的值須大于 35.00。 |
| /bookstore/book[price>35.00]/title | 選取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值須大于 35.00 |
選取未知節點:XPath 通配符可用來選取未知的 XML 元素。
| 通配符 | 描述 |
| * | 匹配任何元素節點。 |
| @* | 匹配任何屬性節點。 |
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
| 路徑表達式 | 結果 |
| /bookstore/* | 選取 bookstore 元素的所有子元素。 |
| //* | 選取文檔中的所有元素。 |
| //title[@*] | 選取所有帶有屬性的 title 元素。 |
選取若干路徑: 通過在路徑表達式中使用“|”運算符,您可以選取若干個路徑。
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
| 路徑表達式 | 結果 |
| //book/title | //book/price | 選取 book 元素的所有 title 和 price 元素。 |
| //title | //price | 選取文檔中的所有 title 和 price 元素。 |
| /bookstore/book/title | //price | 選取屬于 bookstore 元素的 book 元素的所有 title 元素,以及文檔中所有的 price 元素。 |
XPath 高級用法
模糊查詢 contains
目前許多 web 框架,都是動態生成界面的元素id,因此在每次操作相同界面時,ID都是變化的,這樣為自動化測試造成了一定的影響。
<div class="eleWrapper" title="請輸入用戶名"> <input type="text" class="textfield" name="ID9sLJQnkQyLGLhYShhlJ6gPzHLgvhpKpLzp2Tyh4hyb1b4pnvzxFR!-166749344!1357374592067" id="nt1357374592068" /> </div>解決方法 使用 xpath 的匹配功能,//input[contains(@id,'nt')]
測試使用的XML <Root> <Person ID="1001" > <Name lang="zh-cn" >張城斌</Name> <Email xmlns="www.quicklearn.cn" > cbcye@live.com </Email> <Blog>http://cbcye.cnblogs.com</Blog> </Person> <Person ID="1002" > <Name lang="en" >Gary Zhang</Name> <Email xmlns="www.quicklearn.cn" > GaryZhang@cbcye.com</Email> <Blog>http://www.quicklearn.cn</Blog> </Person> </Root>1.查詢所有Blog節點值中帶有 cn 字符串的Person節點。Xpath表達式:/Root//Person[contains(Blog,'cn')]
2.查詢所有Blog節點值中帶有 cn 字符串并且屬性ID值中有01的Person節點。Xpath表達式:/Root//Person[contains(Blog,'cn') and contains(@ID,'01')]
xpath 學習筆記
1.依靠自己的屬性,文本定位//td[text()='Data Import']//div[contains(@class,'cux-rightArrowIcon-on')]//a[text()='馬上注冊']//input[@type='radio' and @value='1'] 多條件//span[@name='bruce'][text()='bruce1'][1] 多條件//span[@id='bruce1' or text()='bruce2'] 找出多個//span[text()='bruce1' and text()='bruce2'] 找出多個 2.依靠父節點定位//div[@class='x-grid-col-name x-grid-cell-inner']/div//div[@id='dynamicGridTestInstanceformclearuxformdiv']/div//div[@id='test']/input 3.依靠子節點定位//div[div[@id='navigation']]//div[div[@name='listType']]//div[p[@name='testname']] 4.混合型//div[div[@name='listType']]//img//td[a//font[contains(text(),'seleleium2從零開始 視屏')]]//input[@type='checkbox'] 5.進階部分//input[@id='123']/following-sibling::input 找下一個兄弟節點//input[@id='123']/preceding-sibling::span 上一個兄弟節點//input[starts-with(@id,'123')] 以什么開頭//span[not(contains(text(),'xpath'))] 不包含xpath字段的span 6.索引//div/input[2]//div[@id='position']/span[3]//div[@id='position']/span[position()=3]//div[@id='position']/span[position()>3]//div[@id='position']/span[position()<3]//div[@id='position']/span[last()]//div[@id='position']/span[last()-1] 7.substring 截取判斷<div data-for="result" id="swfEveryCookieWrap"></div>//*[substring(@id,4,5)='Every']/@id 截取該屬性 定位3,取長度5的字符 //*[substring(@id,4)='EveryCookieWrap'] 截取該屬性從定位3 到最后的字符 //*[substring-before(@id,'C')='swfEvery']/@id 屬性 'C'之前的字符匹配//*[substring-after(@id,'C')='ookieWrap']/@id 屬性'C之后的字符匹配 8.通配符*//span[@*='bruce']//*[@name='bruce'] 9.軸//div[span[text()='+++current node']]/parent::div 找父節點//div[span[text()='+++current node']]/ancestor::div 找祖先節點 10.孫子節點//div[span[text()='current note']]/descendant::div/span[text()='123']//div[span[text()='current note']]//div/span[text()='123'] 兩個表達的意思一樣 11.following pre https://www.baidu.com/s?wd=xpath&pn=10&oq=xpath&ie=utf-8&rsv_idx=1&rsv_pq=df0399f30003691c&rsv_t=7dccXo734hMJVeb6AVGfA3434tA9U%2FXQST0DrOW%2BM8GijQ8m5rVN2R4J3gU//span[@class="fk fk_cur"]/../following::a 往下的所有a//span[@class="fk fk_cur"]/../preceding::a[1] 往上的所有axpath提取多個標簽下的text在寫爬蟲的時候,經常會使用xpath進行數據的提取,對于如下的代碼: <div id="test1">大家好!</div> 使用xpath提取是非常方便的。假設網頁的源代碼在selector中: data = selector.xpath('//div[@id="test1"]/text()').extract()[0] 就可以把“大家好!”提取到data變量中去。 然而如果遇到下面這段代碼呢? <div id="test2">美女,<font color=red>你的微信是多少?</font><div> 如果使用: data = selector.xpath('//div[@id="test2"]/text()').extract()[0] 只能提取到“美女,”; 如果使用: data = selector.xpath('//div[@id="test2"]/font/text()').extract()[0] 又只能提取到“你的微信是多少?” 可是我本意是想把“美女,你的微信是多少?”這一整個句子提取出來。 <div id="test3">我左青龍,<span id="tiger">右白虎,<ul>上朱雀,<li>下玄武。</li></ul>老牛在當中,</span>龍頭在胸口。<div> 而且內部的標簽還不固定,如果我有一百段這樣類似的html代碼,又如何使用xpath表達式,以最快最方便的方式提取出來? 使用xpath的string(.) 以第三段代碼為例: data = selector.xpath('//div[@id="test3"]') info = data.xpath('string(.)').extract()[0] 這樣,就可以把“我左青龍,右白虎,上朱雀,下玄武。老牛在當中,龍頭在胸口”整個句子提取出來,賦值給info變量。2.3 非結構化數據之 lxml 庫
lxml 是一種使用 Python 編寫的庫,可以迅速、靈活地處理 XML ,支持 XPath (XML Path Language),可以利用 XPath 語法快速的定位特定元素以及節點信息,提取出 HTML、XML 目標數據
lxml python 官方文檔?http://lxml.de/index.html
簡單使用
首先利用 lxml 來解析 HTML 代碼,先來一個小例子來感受一下它的基本用法。使用 lxml 的 etree 庫,然后利用 etree.HTML 初始化,然后我們將其打印出來。
from lxml import etree text = ''' <div><ul><li class="item-0"><a href="link1.html">first item</a></li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></ul> </div> ''' # Parses an HTML document from a string html = etree.HTML(text) # Serialize an element to an encoded string representation of its XML tree result = etree.tostring(html) print(result)所以輸出結果如下,不僅補全了 li 標簽,還添加了 body,html 標簽。
<html><body><div><ul><li class="item-0"><a href="link1.html">first item</a></li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul> </div> </body></html>XPath 實例測試
(1)獲取所有的 <li> 標簽
from lxml import etree html_text = ''' <div><ul><li class="item-0"><a href="link1.html">first item</a></li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></ul> </div> ''' # Parses an HTML document from a string html = etree.HTML(text=html_text) # Serialize an element to an encoded string representation of its XML tree # result = etree.tostring(html) # print(result)print(type(html)) result = html.xpath('//li') print(result) print(len(result)) print(type(result)) print(type(result[0]))- 每個元素都是 Element 類型;是一個個的標簽元素,類似現在的實例
? ? ? ? <Element li at 0x1014e0e18> Element類型代表的就是
? ? ? ? <li class="item-0"><a href="link1.html">first item</a></li>
[注意]:Element 類型是一種靈活的容器對象,用于在內存中存儲結構化數據。每個 element 對象都具有以下屬性:
1. tag:string對象,標簽,用于標識該元素表示哪種數據(即元素類型)。
2. attrib:dictionary對象,表示附有的屬性。
3. text:string對象,表示element的內容。
4. tail:string對象,表示element閉合之后的尾跡。
(2)獲取 <li> 標簽的所有 class
html.xpath('//li/@class')# 運行結果:['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0'](3)獲取 <li> 標簽下屬性 href 為 link1.html 的 <a> 標簽
html.xpath('//li/a[@href="link1.html"]')# 運行結果:[<Element a at 0x10ffaae18>](4)獲取 <li> 標簽下的所有 <span> 標簽
注意這么寫是不對的 html.xpath('//li/span') 因為 / 是用來獲取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用雙斜杠html.xpath('//li//span') # 運行結果:[<Element span at 0x10d698e18>](5)獲取 <li> 標簽下的所有 class,不包括 <li>
html.xpath('//li/a//@class') # 運行結果:['blod'](6)獲取最后一個 <li> 的<a> 的 href
html.xpath('//li[last()]/a/@href') # 運行結果:['link5.html'](7)獲取 class 為 bold 的標簽
result = html.xpath('//*[@class="bold"]') print result[0].tag # 運行結果:span騰訊招聘:https://careers.tencent.com/search.html?pcid=40001
2.4 非結構化數據之 CSS Selector(CSS 選擇器)
CSS 即層疊樣式表Cascading Stylesheet。?Selector 來定位(locate)頁面上的元素(Elements)。?Selenium 官網的Document 里極力推薦使用 CSS locator,而不是 XPath 來定位元素,原因是 CSS locator 比 XPath locator 速度快。
Beautiful Soup
安裝:https://pypi.org/project/bs4/
官方文檔鏈接:?https://www.crummy.com/software/BeautifulSoup/bs4/doc/
Beautiful Soup 是從 HTML 或 XML 文件中提取數據,支持 Python 標準庫中的 HTML 解析器。還支持一些第三方的解析器,其中一個是 lxml。
Beautiful Soup 自動將輸入文檔轉換為 Unicode 編碼,輸出文檔轉換為 utf-8 編碼。你不需要考慮編碼方式,除非文檔沒有指定一個編碼方式,這時,Beautiful Soup 就不能自動識別編碼方式了。然后,你僅僅需要說明一下原始編碼方式就可以了。另一個可供選擇的解析器是純 Python 實現的 html5lib , html5lib 的解析方式與瀏覽器相同,可以選擇下列方法來安裝 html5lib: pip install html5lib
下表列出了主要的解析器:
| 解析器 | 使用方法 | 優勢 | 劣勢 |
| Python標準庫 | BeautifulSoup(markup, "html.parser") | Python的內置標準庫;執行速度適中;文檔容錯能力強 | Python 2.7.3 or 3.2.2前 的版本中文檔容錯能力差 |
| lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快;文檔容錯能力強 ; | 需要安裝C語言庫 |
| lxml XML 解析器 | BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml") | 速度快;唯一支持XML的解析器 | 需要安裝C語言庫 |
| html5lib | BeautifulSoup(markup, "html5lib") | 最好的容錯性;以瀏覽器的方式解析文檔;生成HTML5格式的文檔 | 速度慢;不依賴外部擴展 |
推薦使用 lxml 作為解析器,因為效率更高。在 Python2.7.3 之前的版本和 Python3 中 3.2.2 之前的版本,必須安裝 lxml 或html5lib, 因為那些 Python 版本的標準庫中內置的 HTML 解析方法不夠穩定.
示例:使用 BeautifulSoup 解析這段代碼,能夠得到一個 BeautifulSoup 的對象,并能按照標準的縮進格式的結構輸出:
from bs4 import BeautifulSouphtml_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p><p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p><p class="story">...</p> """soup = BeautifulSoup(html_doc,'lxml')print(soup) # 打印 soup 對象的內容打印 soup 對象的內容
格式化輸出soup 對象
print(soup.prettify())CSS 選擇器
在寫 CSS 時:標簽名不加任何修飾。類名前加點。id名前加 “#”。?
利用類似的方法來篩選元素,用到的方法是 soup.select(),返回類型是 list
通過標簽名查找
from bs4 import BeautifulSouphtml_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p><p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p><p class="story">...</p> """soup = BeautifulSoup(html_doc, 'lxml')# print(soup) # 打印 soup 對象的內容 print(soup.select('title')) # [<title>The Dormouse's story</title>] print(soup.select('a')) print(soup.select('b')) # [<b>The Dormouse's story</b>]通過類名查找
print(soup.select('.sister'))通過 id 名查找
print(soup.select('#link1')) #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]直接子標簽查找
print(soup.select("head > title")) #[<title>The Dormouse's story</title>]組合查找:組合查找即標簽名與類名、id名進行的組合原理是一樣的,例如查找 p 標簽中,id 等于 link1的內容,
屬性 和 標簽 不屬于 同一節點 ?二者需要用 ?空格分開
print(soup.select('p #link1')) #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]屬性查找
查找時還可以加入屬性元素,屬性需要用中括號括起來
注意:屬性和標簽屬于同一節點,所以中間不能加空格,否則會無法匹配到
print(soup.select('a[class="sister"]'))print(soup.select('a[href="http://example.com/elsie"]')) #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]同樣,屬性仍然可以與上述查找方式組合。不在同一節點的使用空格隔開,在同一節點的則不用加空格
print soup.select('p a[href="http://example.com/elsie"]') #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]以上的 select 方法返回的結果都是列表形式,可以遍歷形式輸出
用 get_text() 方法來獲取它的內容。
print soup.select('title')[0].get_text() for title in soup.select('title'):print(title.get_text())Tag:Tag 是什么?通俗點講就是 HTML 中的一個個標簽,例如:
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>print type(soup.select('a')[0])輸出:bs4.element.Tag對于 Tag,它有兩個重要的屬性,是 name 和 attrs,下面我們分別來感受一下
1. name
print(soup.name) print(soup.select('a')[0].name)輸出: [document] 'a'soup 對象本身比較特殊,它的 name 即為 [document],對于其他內部標簽,輸出的值便為標簽本身的名稱。
2. attrs
print(soup.select('a')[0].attrs)輸出: {'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'} 在這里,我們把 soup.select('a')[0] 標簽的所有屬性打印輸出了出來,得到的類型是一個字典。 如果我們想要單獨獲取某個屬性,可以這樣,例如我們獲取它的 class 叫什么print soup.select('a')[0].attrs['class']輸出: ['sister']實戰案例:騰訊招聘網站:https://careers.tencent.com/search.html
2.5 正則表達式
掌握了 XPath、CSS選擇器,為什么還要學習正則?
正則表達式,用標準正則解析,一般會把 HTML當 做普通文本,用指定格式匹配當相關文本,適合小片段文本,或者某一串字符(比如電話號碼、郵箱賬戶),或者HTML包含javascript的代碼,無法用CSS選擇器或者XPath
在線正則表達式測試網站:https://tool.oschina.net/regex/
Python 正則表達式 官方文檔:https://docs.python.org/zh-cn/3.8/library/re.html#regular-expression-objects
正則表達式常見概念
邊界匹配
^ -- 與字符串開始的地方匹配,不匹配任何字符;
$ -- 與字符串結束的地方匹配,不匹配任何字符;
\b -- 匹配一個單詞邊界,也就是單詞和空格之間的位置,不匹配任何字符;
"er\b"可以匹配"never"中的"er",但不能匹配"verb"中的"er"。\B -- \b取非,即匹配一個非單詞邊界;
"er\B"能匹配"verb"中的"er",但不能匹配"never"中的"er"。數量詞的貪婪模式與非貪婪模式
正則表達式通常用于在文本中查找匹配的字符串。Python里數量詞默認是貪婪的(在少數語言里也可能是默認非貪婪),總是嘗試匹配盡可能多的字符;非貪婪的則相反,總是嘗試匹配盡可能少的字符。例如:
正則表達式"ab*"如果用于查找"abbbc",將找到"abbb"。而如果使用非貪婪的數量詞"ab*?",將找到"a"。反斜杠問題
與大多數編程語言相同,正則表達式里使用"\"作為轉義字符,這就可能造成反斜杠困擾。
假如你需要匹配文本中的字符"\",那么使用編程語言表示的正則表達式里將需要4個反斜杠"\\\\":前兩個和后兩個分別用于在編程語言里轉義成反斜杠,轉換成兩個反斜杠后再在正則表達式里轉義成一個反斜杠。
Python里的原生字符串很好地解決了這個問題,這個例子中的正則表達式可以使用r"\\"表示。
同樣,匹配一個數字的"\\d"可以寫成r"\d"。有了原生字符串,你再也不用擔心是不是漏寫了反斜杠,寫出來的表達式也更直觀。
Python Re模塊
Python 自帶了 re 模塊,它提供了對正則表達式的支持。
match 函數:re.match 嘗試從字符串的起始位置匹配一個模式,如果不是起始位置匹配成功的話,match() 就返回 none。
下面是此函數的語法:re.match(pattern, string, flags=0)
| 參數 | 描述 |
| pattern | 這是正則表達式來進行匹配。 |
| string | 這是字符串,這將被搜索匹配的模式,在字符串的開頭。 |
| flags | 標志位,用于控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等。 |
匹配成功則 re.match 方法返回一個匹配的對象,否則返回 None。
我們可以使用group(num) 或 groups() 匹配對象函數來獲取匹配表達式。
| 匹配對象的方法 | 描述 |
| group(num=0) | 此方法返回整個匹配(或指定分組num) |
| groups() | 此方法返回所有元組匹配的子組(空,如果沒有) |
示例代碼:
#!/usr/bin/python import reline = "Cats are smarter than dogs"matchObj = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)if matchObj:print("matchObj.group() : {0}".format(matchObj.group()))print("matchObj.group(1) : {0}".format(matchObj.group(1)))print(f"matchObj.group(2) : {matchObj.group(2)}") else:print("No match!!")''' 輸出: matchObj.group() : Cats are smarter than dogs matchObj.group(1) : Cats matchObj.group(2) : smarter '''正則表達式修飾符 - 選項標志
正則表達式字面可以包含一個可選的修飾符來控制匹配的各個方面。修飾符被指定為一個可選的標志。可以使用異或提供多個修飾符(|),如先前所示,并且可以由這些中的一個來表示:
| 修飾符 | 描述 |
| re.I(re.IGNORECASE) | 使匹配對大小寫不敏感 |
| re.M(MULTILINE) | 多行匹配,影響 ^ 和 $ |
| re.S(DOTALL) | 使 . 匹配包括換行在內的所有字符 |
| re.X(VERBOSE) | 正則表達式可以是多行,忽略空白字符,并可以加入注釋 |
findall() 函數
re.findall(pattern, string, flags=0)
返回字符串中所有模式的非重疊的匹配,作為字符串列表。該字符串掃描左到右,并匹配返回的順序發現
默認:pattren = "\w+"target = "hello world\nWORLD HELLO"re.findall(pattren,target)['hello', 'world', 'WORLD', 'HELLO']re.I: re.findall("world", target,re.I)['world', 'WORLD']re.S: re.findall("world.WORLD", target,re.S)["world\nworld"]re.findall("hello.*WORLD", target,re.S)['hello world\nWORLD']re.M:re.findall("^WORLD",target,re.M)["WORLD"]re.X:reStr = '''\d{3} #區號-\d{8}''' #號碼re.findall(reStr,"010-12345678",re.X) ["010-12345678"]search 函數
re.search 掃描整個字符串并返回第一個成功的匹配。
下面是此函數語法:re.search(pattern, string, flags=0)
| 參數 | 描述 |
| pattern | 這是正則表達式來進行匹配。 |
| string | 這是字符串,這將被搜索到的字符串中的任何位置匹配的模式。 |
| flags | 標志位,用于控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等。 |
匹配成功則 re.search 方法返回一個匹配的對象,否則返回None。
我們可以使用group(num) 或 groups() 匹配對象函數來獲取匹配表達式。
#!/usr/bin/python import reline = "Cats are smarter than dogs"searchObj = re.search(r'(.*) are (.*?) .*', line, re.M | re.I)if searchObj:print("matchObj.group() : {0}".format(searchObj.group()))print("matchObj.group(1) : {0}".format(searchObj.group(1)))print(f"matchObj.group(2) : {searchObj.group(2)}") else:print("No match!!")''' 輸出: matchObj.group() : Cats are smarter than dogs matchObj.group(1) : Cats matchObj.group(2) : smarter '''re.match 與 re.search 的區別
- re.match? 只匹配字符串的開始,如果字符串開始不符合正則表達式,則匹配失敗,函數返回None;
- re.search 匹配整個字符串,直到找到一個匹配。
示例代碼:
#!/usr/bin/python import reline = "Cats are smarter than dogs";matchObj = re.match(r'dogs', line, re.M | re.I) if matchObj:print(f"match --> matchObj.group() : {matchObj.group()}") else:print("No match!!")searchObj = re.search(r'dogs', line, re.M | re.I) if searchObj:print(f"search --> searchObj.group() : {searchObj.group()}") else:print("Nothing found!!")''' No match!! search --> searchObj.group() : dogs '''搜索 和 替換
Python 的 re 模塊提供了 re.sub 用于替換字符串中的匹配項。
語法:re.sub(pattern, repl, string, max=0)
返回的字符串是在字符串中用 RE 最左邊不重復的匹配來替換。如果模式沒有發現,字符將被沒有改變地返回。 可選參數 count 是模式匹配后替換的最大次數;count 必須是非負整數。缺省值是 0 表示替換所有的匹配。
實例:
#!/usr/bin/python import reurl = "http://hr.tencent.com/position.php?&start=10" page = re.search(r'start=(\d+)', url).group(1)next_url = re.sub(r'start=(\d+)', 'start=' + str(int(page) + 10), url) print(f"Next Url : {next_url}")# 當執行上面的代碼,產生以下結果: # Next Url : http://hr.tencent.com/position.php?&start=20正則表達式語法
2.6 頁面解析之結構化數據
結構化的數據是最好處理,一般都是類似 JSON 格式的字符串,直接解析 JSON 數據,提取 JSON 的關鍵字段即可。
JSON (JavaScript Object Notation) 是一種輕量級的數據交換格式;適用于進行數據交互的場景,比如網站前臺與后臺之間的數據交互。Python 自帶了 JSON 模塊,直接 import json 就可以使用了。Json 模塊提供了四個功能:dumps、dump、loads、load,用于字符串 和 python數據類型間進行轉換
Python 操作 json 的標準 api 庫參考:https://docs.python.org/zh-cn/3/library/json.html
在線 JSON 格式化代碼:https://tool.oschina.net/codeformat/json
1. json.loads()
作用:json字符串 轉化 python 的類型,返回一個python的類型
從 json 到 python 的類型轉化對照如下:
示例 :
import jsona = "[1,2,3,4]" b = '{"k1":1,"k2":2}' # 當字符串為字典時{}外面必須是''單引號{}里面必須是""雙引號 print(json.loads(a)) # [1, 2, 3, 4]print(json.loads(b)) # {'k2': 2, 'k1': 1}豆瓣讀書 示例:
import re import json import requestsdef crawl():custom_headers = {'Host': 'book.douban.com','Connection': 'keep-alive','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;''q=0.8,application/signed-exchange;v=b3;q=0.9','Referer': 'https://book.douban.com/',}db_url = 'https://book.douban.com/subject/26393282/'r = requests.get(url=db_url, headers=custom_headers, verify=False)if 200 == r.status_code:html_text = r.textresult_list = re.findall(r'<script type="application/ld\+json">([\s\S]*?)</script>', html_text)if len(result_list):json_string = result_list[0]# print(json_string)json_dict = json.loads(json_string)# print(json.dumps(json_dict, ensure_ascii=False, indent=4))print(f'name: {json_dict["name"]}')print(f'author: {json_dict["author"][0]["name"]} {json_dict["author"][1]["name"]}')print(f'url: {json_dict["url"]}')print(f'isbn: {json_dict["isbn"]}')if __name__ == '__main__':crawl()pass2. json.dumps()
實現 python 類型轉化為 json 字符串,返回一個 str 對象
從 python 原始類型向 json 類型的轉化對照如下:
示例:
import json a = [1,2,3,4] b ={"k1":1,"k2":2} c = (1,2,3,4)json.dumps(a) # '[1, 2, 3, 4]'json.dumps(b) # '{"k2": 2, "k1": 1}'json.dumps(c) # '[1, 2, 3, 4]'json.dumps 中的 ensure_ascii 參數引起的中文編碼問題
如果 Python Dict 字典含有中文,json.dumps 序列化時對中文默認使用的 ascii 編碼
import chardet import jsonb = {"name":"中國"}json.dumps(b) # '{"name": "\\u4e2d\\u56fd"}'print json.dumps(b) # {"name": "\u4e2d\u56fd"}chardet.detect(json.dumps(b)) # {'confidence': 1.0, 'encoding': 'ascii'}'中國' 中的 ascii 字符碼,而不是真正的中文。想輸出真正的中文需要指定 ensure_ascii=False
json.dumps(b,ensure_ascii=False) # '{"name": "\xe6\x88\x91"}'print json.dumps(b,ensure_ascii=False) # {"name": "我"}chardet.detect(json.dumps(b,ensure_ascii=False)) # {'confidence': 0.7525, 'encoding': 'utf-8'}3. json.dump()
把 Python 類型 以 字符串的形式 寫到文件中
import json a = [1,2,3,4] json.dump(a,open("digital.json","w")) b = {"name":"我"} json.dump(b,open("name.json","w"),ensure_ascii=False) json.dump(b,open("name2.json","w"),ensure_ascii=True)4. json.load()
讀取 文件中 json 形式的字符串元素 轉化成 python 類型
# -*- coding: utf-8 -*- import json number = json.load(open("digital.json")) print number b = json.load(open("name.json")) print b b.keys() print b['name']實戰項目:獲取 lagou 城市表信息
import json import chardet import requestsurl = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json?' custom_headers = {'Host': 'www.lagou.com','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36',}response = requests.get(url, verify=False, headers=custom_headers)print(response.status_code) res_Html = response.text json_obj = json.loads(res_Html) print(type(json_obj)) print(json_obj)city_list =[]all_cities = json_obj['content']['data']['allCitySearchLabels']print(all_cities.keys())for key in all_cities:print(type(all_cities[key]))for item in all_cities[key]:name = item['name']print(f'name: {name}')city_list.append(name)fp = open('city.json', 'w') content = json.dumps(city_list, ensure_ascii=False) print(content) fp.write(content) fp.close()JSONPath
- JSON 信息抽取類庫,從JSON文檔中抽取指定信息的工具
- JSONPath 與 Xpath 區別:JsonPath 對于 JSON 來說,相當于 XPATH 對于XML。
下載地址:https://pypi.python.org/pypi/jsonpath/
安裝方法:下載 jsonpath,解壓之后執行 'python setup.py install '
參考文檔
| XPath | JSONPath | Result |
| /store/book/author | $.store.book[*].author | the authors of all books in the store |
| //author | $..author | all authors |
| /store/* | $.store.* | all things in store, which are some books and a red bicycle. |
| /store//price | $.store..price | the price of everything in the store. |
| //book[3] | $..book[2] | the third book |
| //book[last()] | $..book[(@.length-1)]$..book[-1:] | the last book in order. |
| //book[position()<3] | $..book[0,1]$..book[:2] | the first two books |
| //book[isbn] | $..book[?(@.isbn)] | filter all books with isbn number |
| //book[price<10] | $..book[?(@.price<10)] | filter all books cheapier than 10 |
| //* | $..* | all Elements in XML document. All members of JSON structure. |
案例
還是以?http://www.lagou.com/lbs/getAllCitySearchLabels.json?為例,獲取所有城市
import json import jsonpath import chardet import requestsurl = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json?' custom_headers = {'Host': 'www.lagou.com','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36',}response = requests.get(url, verify=False, headers=custom_headers)print(response.status_code) res_Html = response.text json_obj = json.loads(res_Html)city_list = jsonpath.jsonpath(json_obj, '$..name')print(city_list) print(type(city_list)) fp = open('city.json', 'w') content = json.dumps(city_list, ensure_ascii=False) print(content) fp.write(content) fp.close()XML
xmltodict 模塊讓使用 XML 感覺跟操作 JSON 一樣
Python 操作 XML 的第三方庫參考:https://github.com/martinblech/xmltodict
模塊安裝:pip install xmltodict
數據提取總結
- HTML、XML:XPath、CSS選擇器、正則表達式、轉化成 Python 類型(xmltodict)
- JSON:JSONPath,轉化成 Python 類型進行操作(json類)
- 其他(js、文本、電話號碼、郵箱地址):正則表達式
3. 爬蟲實踐篇
右鍵 ---> 查看源代碼 和 F12? 區別:
- 右鍵查看源代碼:實質是一個 Get 請求
- F12 是整個頁面 所有的請求 url 加載完成的頁面
3.1 案例 1:(?采集 百度貼吧 信息 )
http://tieba.baidu.com/f?ie=utf-8&kw=%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB&fr=search
解決問題思路:
- 1. 確認需求數據在哪。右鍵查看源代碼
- 2. Fidder 模擬發送數據
代碼示例:https://cuiqingcai.com/993.html
3.2 ?案例 2:(?惠州市網上掛牌交易系統? )
為例:http://www.hdgtjy.com/index/Index4/? ?采集所有的掛牌交易信息
import json import requestsdef crawl():url = 'http://www.hdgtjy.com/Index/PublicResults'custom_headers = {'X-Requested-With': 'XMLHttpRequest','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36','Content-Type': 'application/x-www-form-urlencoded',}form_data = {'page': 1,'size': 500}r = requests.post(url=url, data=form_data, headers=custom_headers)if 200 == r.status_code:json_string = r.textjson_dict = json.loads(json_string)print(json.dumps(json_dict, ensure_ascii=False, indent=4))else:print(f'status_code:{r.status_code}')if __name__ == '__main__':crawl()pass3.3 案例 3:(?Requests 基本用法 與 藥品監督管理局 )
Requests
優點:Requests 繼承了 urllib2 的所有特性。Requests 支持 HTTP 連接保持和連接池,支持使用 cookie 保持會話,支持文件上傳,支持自動確定響應內容的編碼,支持國際化的 URL 和 POST 數據自動編碼。
缺陷:requests 不是 python 自帶的庫,需要另外安裝 easy_install or pip install。直接使用不能異步調用,速度慢(自動確定響應內容的編碼)。pip install requests
文檔:http://cn.python-requests.org/zh_CN/latest/index.html? ??http://www.python-requests.org/en/master/#
使用方法:
requests.get(url, data={'key1': 'value1'},headers={'User-agent','Mozilla/5.0'}) requests.post(url, data={'key1': 'value1'},headers={'content-type': 'application/json'})以 藥品監督管理局 為例,采集 藥品 --->?國產藥品 下的所有的商品信息:http://app1.nmpa.gov.cn/data_nmpa/face3/base.jsp?tableId=25&tableName=TABLE25&title=國產藥品&bcId=152904713761213296322795806604
# -*- coding: utf-8 -*- import urllib from lxml import etree import re import json import chardet import requestscurstart = 2values = {'tableId': '32','State': '1','bcId': '124356639813072873644420336632','State': '1','tableName': 'TABLE32','State': '1','viewtitleName': 'COLUMN302','State': '1','viewsubTitleName': 'COLUMN299,COLUMN303','State': '1','curstart': str(curstart),'State': '1','tableView': urllib.quote("國產藥品商品名"),'State': '1', }post_headers = {'Content-Type': 'application/x-www-form-urlencoded','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' } url = "http://app1.sfda.gov.cn/datasearch/face3/search.jsp"response = requests.post(url, data=values, headers=post_headers)resHtml = response.text print response.status_code # print resHtmlUrls = re.findall(r'callbackC,\'(.*?)\',null', resHtml) for url in Urls:# 坑print url.encode('gb2312')查看運行結果,感受一下。?
總結
1. User-Agent 偽裝 Chrome,欺騙 web 服務器
2. urlencode 字典類型 Dict、元祖 轉化成 url query 字符串
1. 完成商品詳情頁采集
2. 完成整個項目的采集
詳情頁
# -*- coding: utf-8 -*- from lxml import etree import re import json import requestsurl ='http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=211315' get_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36','Connection': 'keep-alive', } item = {} response = requests.get(url,headers=get_headers) resHtml = response.text print response.encoding html = etree.HTML(resHtml) for site in html.xpath('//tr')[1:]:if len(site.xpath('./td'))!=2:continuename = site.xpath('./td')[0].textif not name:continue# value =site.xpath('./td')[1].textvalue = re.sub('<.*?>', '', etree.tostring(site.xpath('./td')[1],encoding='utf-8'))item[name.encode('utf-8')] = valuejson.dump(item,open('sfda.json','w'),ensure_ascii=False)完整項目
# -*- coding: utf-8 -*- import urllib from lxml import etree import re import json import requestsdef ParseDetail(url):# url = 'http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=211315'get_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36','Connection': 'keep-alive',}item = {}response = requests.get(url, headers=get_headers)resHtml = response.textprint response.encodinghtml = etree.HTML(resHtml)for site in html.xpath('//tr')[1:]:if len(site.xpath('./td')) != 2:continuename = site.xpath('./td')[0].textif not name:continue# value =site.xpath('./td')[1].textvalue = re.sub('<.*?>', '', etree.tostring(site.xpath('./td')[1], encoding='utf-8'))value = re.sub('', '', value)item[name.encode('utf-8').strip()] = value.strip()# json.dump(item, open('sfda.json', 'a'), ensure_ascii=False)fp = open('sfda.json', 'a')str = json.dumps(item, ensure_ascii=False)fp.write(str + '\n')fp.close()def main():curstart = 2values = {'tableId': '32','State': '1','bcId': '124356639813072873644420336632','State': '1','tableName': 'TABLE32','State': '1','viewtitleName': 'COLUMN302','State': '1','viewsubTitleName': 'COLUMN299,COLUMN303','State': '1','curstart': str(curstart),'State': '1','tableView': urllib.quote("國產藥品商品名"),'State': '1',}post_headers = {'Content-Type': 'application/x-www-form-urlencoded','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'}url = "http://app1.sfda.gov.cn/datasearch/face3/search.jsp"response = requests.post(url, data=values, headers=post_headers)resHtml = response.textprint response.status_code# print resHtmlUrls = re.findall(r'callbackC,\'(.*?)\',null', resHtml)for url in Urls:# 坑url = re.sub('tableView=.*?&', 'tableView=' + urllib.quote("國產藥品商品名") + "&", url)ParseDetail('http://app1.sfda.gov.cn/datasearch/face3/' + url.encode('gb2312'))if __name__ == '__main__':main()3.4 案例 4:(?拉鉤招聘網 )
以拉鉤具體詳情頁為例,進行抓取。http://www.lagou.com/jobs/2101463.html
from lxml import etree import requests import reresponse = requests.get('http://www.lagou.com/jobs/2101463.html') resHtml = response.texthtml = etree.HTML(resHtml)title = html.xpath('//h1[@title]')[0].attrib['title'] #salary= html.xpath('//span[@class="red"]')[0].textsalary = html.xpath('//dd[@class="job_request"]/p/span')[0].text worklocation = html.xpath('//dd[@class="job_request"]/p/span')[1].text experience = html.xpath('//dd[@class="job_request"]/p/span')[2].text education = html.xpath('//dd[@class="job_request"]/p/span')[3].text worktype = html.xpath('//dd[@class="job_request"]/p/span')[4].text Temptation = html.xpath('//dd[@class="job_request"]/p[2]')[0].textprint salary,worklocation,experience,education,worktype,Temptationdescription_tag = html.xpath('//dd[@class="job_bt"]')[0] description = etree.tostring( description_tag,encoding='utf-8') #print description deal_descp = re.sub('<.*?>','',description) print deal_descp.strip() publisher_name = html.xpath('//*[@class="publisher_name"]//@title')[0] pos = html.xpath('//*[@class="pos"]')[0].text chuli_lv = html.xpath('//*[@class="data"]')[0].text chuli_yongshi = html.xpath('//*[@class="data"]')[1].textprint chuli_lv,chuli_yongshi,pos,publisher_name3.5?案例 5:(?爬取糗事百科段子 )
確定URL并抓取頁面代碼,首先我們確定好頁面的URL是 http://www.qiushibaike.com/8hr/page/4, 其中最后一個數字1代表頁數,我們可以傳入不同的值來獲得某一頁的段子內容。我們初步構建如下的代碼來打印頁面代碼內容試試看,先構造最基本的頁面抓取方式,看看會不會成功。在Composer raw 模擬發送數據
GET http://www.qiushibaike.com/8hr/page/2/ HTTP/1.1 Host: www.qiushibaike.com User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Accept-Language: zh-CN,zh;q=0.8在刪除了 User-Agent、Accept-Language 報錯。應該是 headers 驗證的問題,加上一個 headers 驗證試試看
# -*- coding:utf-8 -*- import urllib import requests import re import chardet from lxml import etreepage = 2 url = 'http://www.qiushibaike.com/8hr/page/' + str(page) + "/" headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36','Accept-Language': 'zh-CN,zh;q=0.8'} try:response = requests.get(url, headers=headers)resHtml = response.texthtml = etree.HTML(resHtml)result = html.xpath('//div[contains(@id,"qiushi_tag")]')for site in result:#print etree.tostring(site,encoding='utf-8')item = {}imgUrl = site.xpath('./div/a/img/@src')[0].encode('utf-8')username = site.xpath('./div/a/@title')[0].encode('utf-8')#username = site.xpath('.//h2')[0].textcontent = site.xpath('.//div[@class="content"]')[0].text.strip().encode('utf-8')vote = site.xpath('.//i')[0].text#print site.xpath('.//*[@class="number"]')[0].textcomments = site.xpath('.//i')[1].textprint imgUrl, username, content, vote, commentsexcept Exception, e:print e多線程爬蟲 實戰:糗事百科
python 下多線程的思考
Queue 是 python 中的標準庫,可以直接 import Queue 引用; 隊列是線程間最常用的交換數據的形式。對于共享資源,加鎖是個重要的環節。因為 python 原生的 list,dict 等,都是 not thread safe 的。而 Queue,是線程安全的,因此在滿足使用條件下,建議使用隊列。
Python Queue 模塊有三種隊列及構造函數:
1、Python Queue模塊的FIFO隊列先進先出。 class Queue.Queue(maxsize) 2、LIFO類似于堆,即先進后出。 class Queue.LifoQueue(maxsize) 3、還有一種是優先級隊列級別越低越先出來。 class Queue.PriorityQueue(maxsize)Queue(隊列對象)
初始化: class Queue.Queue(maxsize) FIFO 先進先出
包中的常用方法:
Queue.qsize() 返回隊列的大小 Queue.empty() 如果隊列為空,返回True,反之False Queue.full() 如果隊列滿了,返回True,反之False Queue.full 與 maxsize 大小對應 Queue.get([block[, timeout]])獲取隊列,timeout等待時間調用隊列對象的 get()方法從隊頭刪除并返回一個項目??蛇x參數為block,默認為True。
如果隊列為空且 block 為 True,get() 就使調用線程暫停,直至有項目可用。
如果隊列為空且 block 為 False,隊列將引發 Empty 異常。
示例代碼:
# -*- coding:utf-8 -*- import requests from lxml import etree from Queue import Queue import threading import time import jsonclass thread_crawl(threading.Thread):'''抓取線程類'''def __init__(self, threadID, q):threading.Thread.__init__(self)self.threadID = threadIDself.q = qdef run(self):print "Starting " + self.threadIDself.qiushi_spider()print "Exiting ", self.threadIDdef qiushi_spider(self):# page = 1while True:if self.q.empty():breakelse:page = self.q.get()print 'qiushi_spider=', self.threadID, ',page=', str(page)url = 'http://www.qiushibaike.com/hot/page/' + str(page) + '/'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36','Accept-Language': 'zh-CN,zh;q=0.8'}# 多次嘗試失敗結束、防止死循環timeout = 4while timeout > 0:timeout -= 1try:content = requests.get(url, headers=headers)data_queue.put(content.text)breakexcept Exception, e:print 'qiushi_spider', eif timeout < 0:print 'timeout', urlclass Thread_Parser(threading.Thread):'''頁面解析類;'''def __init__(self, threadID, queue, lock, f):threading.Thread.__init__(self)self.threadID = threadIDself.queue = queueself.lock = lockself.f = fdef run(self):print 'starting ', self.threadIDglobal total, exitFlag_Parserwhile not exitFlag_Parser:try:'''調用隊列對象的get()方法從隊頭刪除并返回一個項目??蛇x參數為block,默認為True。如果隊列為空且block為True,get()就使調用線程暫停,直至有項目可用。如果隊列為空且block為False,隊列將引發Empty異常。'''item = self.queue.get(False)if not item:passself.parse_data(item)self.queue.task_done()print 'Thread_Parser=', self.threadID, ',total=', totalexcept:passprint 'Exiting ', self.threadIDdef parse_data(self, item):'''解析網頁函數:param item: 網頁內容:return:'''global totaltry:html = etree.HTML(item)result = html.xpath('//div[contains(@id,"qiushi_tag")]')for site in result:try:imgUrl = site.xpath('.//img/@src')[0]title = site.xpath('.//h2')[0].textcontent = site.xpath('.//div[@class="content"]')[0].text.strip()vote = Nonecomments = Nonetry:vote = site.xpath('.//i')[0].textcomments = site.xpath('.//i')[1].textexcept:passresult = {'imgUrl': imgUrl,'title': title,'content': content,'vote': vote,'comments': comments,}with self.lock:# print 'write %s' % json.dumps(result)self.f.write(json.dumps(result, ensure_ascii=False).encode('utf-8') + "\n")except Exception, e:print 'site in result', eexcept Exception, e:print 'parse_data', ewith self.lock:total += 1data_queue = Queue() exitFlag_Parser = False lock = threading.Lock() total = 0def main():output = open('qiushibaike.json', 'a')#初始化網頁頁碼page從1-10個頁面pageQueue = Queue(50)for page in range(1, 11):pageQueue.put(page)#初始化采集線程crawlthreads = []crawlList = ["crawl-1", "crawl-2", "crawl-3"]for threadID in crawlList:thread = thread_crawl(threadID, pageQueue)thread.start()crawlthreads.append(thread)#初始化解析線程parserListparserthreads = []parserList = ["parser-1", "parser-2", "parser-3"]#分別啟動parserListfor threadID in parserList:thread = Thread_Parser(threadID, data_queue, lock, output)thread.start()parserthreads.append(thread)# 等待隊列清空while not pageQueue.empty():pass# 等待所有線程完成for t in crawlthreads:t.join()while not data_queue.empty():pass# 通知線程是時候退出global exitFlag_ParserexitFlag_Parser = Truefor t in parserthreads:t.join()print "Exiting Main Thread"with lock:output.close()if __name__ == '__main__':main()3.5 模擬登陸 及 驗證碼
使用表單登陸
這種情況屬于 post 請求,即先向服務器發送表單數據,服務器再將返回的 cookie 存入本地。
data = {'data1':'XXXXX', 'data2':'XXXXX'}Requests:data 為 dict,json
import requestsresponse = requests.post(url=url, data=data)使用 cookie 登陸
使用 cookie 登陸,服務器會認為你是一個已登陸的用戶,所以就會返回給你一個已登陸的內容。因此,需要驗證碼的情況可以使用帶驗證碼登陸的 cookie 解決。
import requests requests_session = requests.session() response = requests_session.post(url=url_login, data=data)若存在驗證碼,此時采用 response = requests_session.post(url=url_login, data=data)是不行的,做法應該如下:
response_captcha = requests_session.get(url=url_login, cookies=cookies) response1 = requests.get(url_login) # 未登陸 response2 = requests_session.get(url_login) # 已登陸,因為之前拿到了Response Cookie! response3 = requests_session.get(url_results) # 已登陸,因為之前拿到了Response Cookie!實戰項目:登錄豆瓣,并發表動態
模擬登陸的重點,在于找到表單真實的提交地址,然后攜帶cookie,post數據即可,只要登陸成功,我們就可以訪問其他任意網頁,從而獲取網頁內容。
一個請求,只要正確模擬了method,url,header,body 這四要素,任何內容都能抓下來,而所有的四個要素,只要打開瀏覽器-審查元素-Network就能看到!
驗證碼這一塊,現在主要是先把驗證碼的圖片保存下來,手動輸入驗證碼,后期可以研究下 python 自動識別驗證碼。
但是驗證碼保存成本地圖片,看的不不太清楚(有時間在改下),可以把驗證碼的 url 地址在瀏覽器中打開,就可以看清楚驗證碼了。
主要實現 登錄豆瓣,并發表一句話
# -*- coding:utf-8 -*-import re import requests from bs4 import BeautifulSoupclass DouBan(object):def __init__(self):self.__username = "豆瓣帳號" # 豆瓣帳號self.__password = "豆瓣密碼" # 豆瓣密碼self.__main_url = "https://www.douban.com"self.__login_url = "https://www.douban.com/accounts/login"self.__proxies = {"http": "http://172.17.18.80:8080","https": "https://172.17.18.80:8080"}self.__headers = {"Host": "www.douban.com","Origin": self.__main_url,"Referer": self.__main_url,"Upgrade-Insecure-Requests": "1","User-Agent": 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}self.__data = {"source": "index_nav","redir": "https://www.douban.com","form_email": self.__username,"form_password": self.__password,"login": u"登錄"}self.__session = requests.session()self.__session.headers = self.__headersself.__session.proxies = self.__proxiespassdef login(self):r = self.__session.post(self.__login_url, self.__data)if r.status_code == 200:html = r.textsoup = BeautifulSoup(html, "lxml")captcha_address = soup.find('img', id='captcha_image')['src']print(captcha_address) # 驗證碼存在if captcha_address:# 利用正則表達式獲取captcha的IDre_captcha_id = r'<input type="hidden" name="captcha-id" value="(.*?)"/'captcha_id = re.findall(re_captcha_id, html)[0]print(captcha_id) # 保存到本地with open('captcha.jpg', 'w') as f:f.write(requests.get(captcha_address, proxies=self.__proxies).text)captcha = input('please input the captcha:')self.__data['captcha-solution'] = captchaself.__data['captcha-id'] = captcha_idr = self.__session.post(self.__login_url, data=self.__data)if r.status_code == 200:print("login success") data = {"ck": "NBJ2","comment": "模擬登錄"}r = self.__session.post(self.__main_url, data=data)print(r.status_code) else:print("登錄不需要驗證碼") # 不需要驗證碼的邏輯 和 上面輸入驗證碼之后 的 邏輯 一樣# 此處代碼省略else:print("login fail", r.status_code) passif __name__ == "__main__":t = DouBan()t.login()pass運行截圖:
登錄豆瓣帳號,可以看到說了一句話 “模擬登錄”
Python 性能優化
因為 GIL 的存在,Python很難充分利用多核 CPU 的優勢。
但是,可以通過內置的模塊 multiprocessing 實現下面幾種并行模式:
- 1、 多進程并行編程:對于CPU密集型的程序,可以使用multiprocessing的Process、Pool 等封裝好的類,通過多進程的方式實現并行計算。但是因為進程中的通信成本比較大,對于進程之間需要大量數據交互的程序效率未必有大的提高。
- 2、 多線程并行編程:對于IO密集型的程序,multiprocessing.dummy 模塊使用 multiprocessing 的接口封裝 threading,使得多線程編程也變得非常輕松(比如可以使用Pool的map接口,簡潔高效)。
- 3、分布式:multiprocessing 中的 Managers 類 提供了可以在不同進程之共享數據的方式,可以在此基礎上開發出分布式的程序。 不同的業務場景可以選擇其中的一種或幾種的組合實現程序性能的優化。
總結
以上是生活随笔為你收集整理的爬虫教程( 1 ) --- 初级、基础、实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ftw遍历目录树
- 下一篇: 7.python之正则表达式re模块