爬虫教程( 1 ) --- 初级、基础、实践
爬蟲(chóng)教程:https://piaosanlang.gitbooks.io/spiders/content/
如何入門(mén) Python 爬蟲(chóng):https://zhuanlan.zhihu.com/p/21479334
靜覓 崔慶才的個(gè)人博客 Python 爬蟲(chóng)系列:http://cuiqingcai.com/category/technique/python
http://www.cnblogs.com/miqi1992/category/1105419.html
Python 爬蟲(chóng)從入門(mén)到放棄系列博客:https://www.cnblogs.com/zhaof/tag/爬蟲(chóng)/default.html?page=2
Python 爬取功能匯總:https://www.jb51.net/Special/985.htm
Python 3.8.5 文檔
官方文檔:https://docs.python.org/zh-cn/3/
1. 爬蟲(chóng)入門(mén) 初級(jí)篇
IDE 選擇: PyCharm (推薦) 、SublimeText3、Visual Studio
Python 版本:Python3。( 最簡(jiǎn)單的是直接安裝 Anaconda,使用 Anaconda 管理虛擬環(huán)境?)
- Windows 平臺(tái):https://docs.python.org/zh-cn/3/using/windows.html
- Linux Ubuntu 平臺(tái):https://docs.python.org/zh-cn/3/using/unix.html
1.1 為什么要學(xué)習(xí)爬蟲(chóng)
學(xué)習(xí)需求:抓取的某個(gè)網(wǎng)站或者某個(gè)應(yīng)用的內(nèi)容,提取有用的價(jià)值
實(shí)現(xiàn)手段:模擬用戶(hù)在瀏覽器或者應(yīng)用(app)上的操作,實(shí)現(xiàn)自動(dòng)化的程序爬蟲(chóng)應(yīng)用場(chǎng)景(利用爬蟲(chóng)能做什么?)
大家最熟悉的應(yīng)用場(chǎng)景:搶票神器(360搶票器)、投票神器(微信朋友圈投票)
企業(yè)應(yīng)用場(chǎng)景
- 拉勾網(wǎng)招聘職位數(shù)據(jù)分析報(bào)告
- 2016年中國(guó)外賣(mài)O2O行業(yè)發(fā)展報(bào)告
-
2016年中國(guó)在線出境游市場(chǎng)研究報(bào)告
1、各種熱門(mén)公司招聘中的職位數(shù)及月薪分布
2、對(duì)某個(gè) App 的下載量跟蹤
3、 飲食地圖
4、 票房預(yù)測(cè)
1.2 爬蟲(chóng)是什么 ?
專(zhuān)業(yè)術(shù)語(yǔ): 網(wǎng)絡(luò)爬蟲(chóng)(又被稱(chēng)為網(wǎng)頁(yè)蜘蛛,網(wǎng)絡(luò)機(jī)器人)是一種按照一定的規(guī)則,自動(dòng)的抓取萬(wàn)維網(wǎng)信息的程序或者腳本。
爬蟲(chóng)起源(產(chǎn)生背景):隨著網(wǎng)絡(luò)的迅速發(fā)展,萬(wàn)維網(wǎng)成為大量信息的載體,如何有效地提取并利用這些信息成為一個(gè)巨大的挑戰(zhàn);搜索引擎有Yahoo,Google,百度等,作為一個(gè)輔助人們檢索信息的工具成為用戶(hù)訪問(wèn)萬(wàn)維網(wǎng)的入口和指南。網(wǎng)絡(luò)爬蟲(chóng)是搜索引擎系統(tǒng)中十分重要的組成部分,它負(fù)責(zé)從互聯(lián)網(wǎng)中搜集網(wǎng)頁(yè),采集信息,這些網(wǎng)頁(yè)信息用于建立索引從而為搜索 引擎提供支持,它決定著整個(gè)引擎系統(tǒng)的內(nèi)容是否豐富,信息是否即時(shí),因此其性能的優(yōu)劣直接影響著搜索引擎的效果。
網(wǎng)絡(luò)爬蟲(chóng)程序的優(yōu)劣,很大程度上反映了一個(gè)搜索引擎的好差。不信,你可以隨便拿一個(gè)網(wǎng)站去查詢(xún)一下各家搜索對(duì)它的網(wǎng)頁(yè)收錄情況,爬蟲(chóng)強(qiáng)大程度跟搜索引擎好壞基本成正比。
搜索引擎工作原理
- 第一步:抓取網(wǎng)頁(yè)(爬蟲(chóng))。搜索引擎是通過(guò)一種特定規(guī)律的軟件跟蹤網(wǎng)頁(yè)的鏈接,從一個(gè)鏈接爬到另外一個(gè)鏈接,像蜘蛛在蜘蛛網(wǎng)上爬行一樣,所以被稱(chēng)為“蜘蛛”也被稱(chēng)為“機(jī)器人”。搜索引擎蜘蛛的爬行是被輸入了一定的規(guī)則的,它需要遵從一些命令或文件的內(nèi)容。 ? ? ? Robots協(xié)議(也稱(chēng)為爬蟲(chóng)協(xié)議、機(jī)器人協(xié)議等)的全稱(chēng)是“網(wǎng)絡(luò)爬蟲(chóng)排除標(biāo)準(zhǔn)”(Robots Exclusion Protocol),網(wǎng)站通過(guò)Robots協(xié)議告訴搜索引擎哪些頁(yè)面可以抓取,哪些頁(yè)面不能抓取。
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 - 第二步:數(shù)據(jù)存儲(chǔ)。搜索引擎是通過(guò)蜘蛛跟蹤鏈接爬行到網(wǎng)頁(yè),并將爬行的數(shù)據(jù)存入原始頁(yè)面數(shù)據(jù)庫(kù)。其中的頁(yè)面數(shù)據(jù)與用戶(hù)瀏覽器得到的 HTML 是完全一樣的。搜索引擎蜘蛛在抓取頁(yè)面時(shí),也做一定的重復(fù)內(nèi)容檢測(cè),一旦遇到權(quán)重很低的網(wǎng)站上有大量抄襲、采集或者復(fù)制的內(nèi)容,很可能就不再爬行。
- 第三步:預(yù)處理。搜索引擎將蜘蛛抓取回來(lái)的頁(yè)面,進(jìn)行各種步驟的預(yù)處理。? ?⒈提取文字,?⒉中文分詞, ⒊去停止詞, ⒋消除噪音(搜索引擎需要識(shí)別并消除這些噪聲,比如版權(quán)聲明文字、導(dǎo)航條、廣告等……), 5 正向索引,? 6 倒排索引, 7 鏈接關(guān)系計(jì)算, 8 特殊文件處理等。?除了HTML文件外,搜索引擎通常還能抓取和索引以文字為基礎(chǔ)的多種文件類(lèi)型,如 PDF、Word、WPS、XLS、PPT、TXT 文件等。我們?cè)谒阉鹘Y(jié)果中也經(jīng)常會(huì)看到這些文件類(lèi)型。但搜索引擎還不能處理圖片、視頻、Flash 這類(lèi)非文字內(nèi)容,也不能執(zhí)行腳本和程序。
- 第四步:排名,提供檢索服務(wù)
但是,這些通用性搜索引擎也存在著一定的局限性,如:
- (1) 不同領(lǐng)域、不同背景的用戶(hù)往往具有不同的檢索目的和需求,通用搜索引擎所返回的結(jié)果包含大量用戶(hù)不關(guān)心的網(wǎng)頁(yè)。
- (2) 通用搜索引目標(biāo)是盡可能的網(wǎng)絡(luò)覆蓋率,有限的搜索引擎服務(wù)器資源與無(wú)限的網(wǎng)絡(luò)數(shù)據(jù)資源之間的矛盾將進(jìn)一步加深。
- (3) 萬(wàn)維網(wǎng)數(shù)據(jù)形式的豐富和網(wǎng)絡(luò)技術(shù)的不斷發(fā)展,圖片、數(shù)據(jù)庫(kù)、音頻、視頻多媒體等不同數(shù)據(jù)大量出現(xiàn),通用搜索引擎往往對(duì)這些信息含量密集且具有一定結(jié)構(gòu)的數(shù)據(jù)無(wú)能為力,不能很好地發(fā)現(xiàn)和獲取。
- (4) 通用搜索引擎大多提供基于關(guān)鍵字的檢索,難以支持根據(jù)語(yǔ)義信息提出的查詢(xún)。
為了解決上述問(wèn)題,定向抓取相關(guān)網(wǎng)頁(yè)資源的聚焦爬蟲(chóng)應(yīng)運(yùn)而生。 聚焦爬蟲(chóng)是一個(gè)自動(dòng)下載網(wǎng)頁(yè)的程序,它根據(jù)既定的抓取目標(biāo),有選擇的訪問(wèn)萬(wàn)維網(wǎng)上的網(wǎng)頁(yè)與相關(guān)的鏈接,獲取所需要的信息。
與通用爬蟲(chóng)(general purpose web crawler)不同,聚焦爬蟲(chóng)并不追求大的覆蓋,而將目標(biāo)定為抓取與某一特定主題內(nèi)容相關(guān)的網(wǎng)頁(yè),為面向主題的用戶(hù)查詢(xún)準(zhǔn)備數(shù)據(jù)資源。
聚焦爬蟲(chóng)工作原理以及關(guān)鍵技術(shù)概述:
網(wǎng)絡(luò)爬蟲(chóng)是一個(gè)自動(dòng)提取網(wǎng)頁(yè)的程序,它為搜索引擎從萬(wàn)維網(wǎng)上下載網(wǎng)頁(yè),是搜索引擎的重要組成。傳統(tǒng)爬蟲(chóng)從一個(gè)或若干初始網(wǎng)頁(yè)的URL開(kāi)始,獲得初始網(wǎng)頁(yè)上的URL,在抓取網(wǎng)頁(yè)的過(guò)程中,不斷從當(dāng)前頁(yè)面上抽取新的URL放入隊(duì)列,直到滿(mǎn)足系統(tǒng)的一定停止條件。聚焦爬蟲(chóng)的工作流程較為復(fù)雜,需要根據(jù)一定的網(wǎng)頁(yè)分析算法過(guò)濾與主題無(wú)關(guān)的鏈接,保留有用的鏈接并將其放入等待抓取的URL隊(duì)列。然后,它將根據(jù)一定的搜索策略從隊(duì)列中選擇下一步要抓取的網(wǎng)頁(yè)URL,并重復(fù)上述過(guò)程,直到達(dá)到系統(tǒng)的某一條件時(shí)停止。另外,所有被爬蟲(chóng)抓取的網(wǎng)頁(yè)將會(huì)被系統(tǒng)存貯,進(jìn)行一定的分析、過(guò)濾,并建立索引,以便之后的查詢(xún)和檢索;對(duì)于聚焦爬蟲(chóng)來(lái)說(shuō),這一過(guò)程所得到的分析結(jié)果還可能對(duì)以后的抓取過(guò)程給出反饋和指導(dǎo)。
相對(duì)于通用網(wǎng)絡(luò)爬蟲(chóng),聚焦爬蟲(chóng)還需要解決三個(gè)主要問(wèn)題:
- (1) 對(duì)抓取目標(biāo)的描述或定義;
- (2) 對(duì)網(wǎng)頁(yè)或數(shù)據(jù)的分析與過(guò)濾;
- (3) 對(duì)URL的搜索策略。
抓取目標(biāo)的描述和定義是決定網(wǎng)頁(yè)分析算法與URL搜索策略如何制訂的基礎(chǔ)。而網(wǎng)頁(yè)分析算法和候選URL排序算法是決定搜索引擎所提供的服務(wù)形式和爬蟲(chóng)網(wǎng)頁(yè)抓取行為的關(guān)鍵所在。這兩個(gè)部分的算法又是緊密相關(guān)的。
網(wǎng)絡(luò)爬蟲(chóng)的發(fā)展趨勢(shì)
隨著 AJAX/Web2.0 的流行,如何抓取 AJAX 等動(dòng)態(tài)頁(yè)面成了搜索引擎急需解決的問(wèn)題,如果搜索引擎依舊采用“爬”的機(jī)制,是無(wú)法抓取到 AJAX 頁(yè)面的有效數(shù)據(jù)的。 對(duì)于 AJAX 這樣的技術(shù),所需要的爬蟲(chóng)引擎必須是基于驅(qū)動(dòng)的。而如果想要實(shí)現(xiàn)事件驅(qū)動(dòng),首先需要解決以下問(wèn)題:
- 第一:JavaScript 的交互分析和解釋;
- 第二:DOM 事件的處理和解釋分發(fā);
- 第三:動(dòng)態(tài) DOM 內(nèi)容語(yǔ)義的抽取。
爬蟲(chóng)發(fā)展的幾個(gè)階段(博士論文copy)
- 第一個(gè)階段:可以說(shuō)是 早期爬蟲(chóng),斯坦福的幾位同學(xué)完成的抓取,當(dāng)時(shí)的互聯(lián)網(wǎng)基本都是完全開(kāi)放的,人類(lèi)流量是主流;
- 第二個(gè)階段:是?分布式爬蟲(chóng),但是爬蟲(chóng)面對(duì)新的問(wèn)題是數(shù)據(jù)量越來(lái)越大,傳統(tǒng)爬蟲(chóng)已經(jīng)解決不了把數(shù)據(jù)都抓全的問(wèn)題,需要更多的爬蟲(chóng),于是調(diào)度問(wèn)題就出現(xiàn)了;
- 第三階段:是?Deep Web?爬蟲(chóng)。此時(shí)面對(duì)新的問(wèn)題是數(shù)據(jù)之間的link越來(lái)越少,比如淘寶,點(diǎn)評(píng)這類(lèi)數(shù)據(jù),彼此link很少,那么抓全這些數(shù)據(jù)就很難;還有一些數(shù)據(jù)是需要提交查詢(xún)?cè)~才能獲取,比如機(jī)票查詢(xún),那么需要尋找一些手段“發(fā)現(xiàn)”更多,更完整的不是明面上的數(shù)據(jù)。
- 第四階段:智能爬蟲(chóng),這主要是爬蟲(chóng)又開(kāi)始面對(duì)新的問(wèn)題:社交網(wǎng)絡(luò)數(shù)據(jù)的抓取。
社交網(wǎng)絡(luò)對(duì)爬蟲(chóng)帶來(lái)的新的挑戰(zhàn)包括
- 有一條賬號(hào)護(hù)城河。我們通常稱(chēng)UGC(User Generated Content)指用戶(hù)原創(chuàng)內(nèi)容。為 web2.0,即數(shù)據(jù)從單向傳達(dá),到雙向互動(dòng),人民群眾可以與網(wǎng)站產(chǎn)生交互,因此產(chǎn)生了賬號(hào),每個(gè)人都通過(guò)賬號(hào)來(lái)標(biāo)識(shí)身份,提交數(shù)據(jù),這樣一來(lái)社交網(wǎng)絡(luò)就可以通過(guò)封賬號(hào)來(lái)提高數(shù)據(jù)抓取的難度,通過(guò)賬號(hào)來(lái)發(fā)現(xiàn)非人類(lèi)流量。之前沒(méi)有賬號(hào)只能通過(guò)cookie和ip。cookie又是易變,易揮發(fā)的,很難長(zhǎng)期標(biāo)識(shí)一個(gè)用戶(hù)。
- 網(wǎng)絡(luò)走向封閉。新浪微博在 2012 年以前都是基本不封的,隨便寫(xiě)一個(gè)程序怎么抓都不封,但是很快,越來(lái)越多的站點(diǎn)都開(kāi)始防止競(jìng)爭(zhēng)對(duì)手,防止爬蟲(chóng)來(lái)抓取,數(shù)據(jù)逐漸走向封閉,越來(lái)越多的人難以獲得數(shù)據(jù)。甚至都出現(xiàn)了專(zhuān)業(yè)的爬蟲(chóng)公司,這在2010年以前是不可想象的。。
- 反爬手段,封殺手法千差萬(wàn)別。寫(xiě)一個(gè)通用的框架抓取成百上千萬(wàn)的網(wǎng)站已經(jīng)成為歷史,或者說(shuō)已經(jīng)是一個(gè)技術(shù)相對(duì)成熟的工作,也就是已經(jīng)有相對(duì)成熟的框架來(lái)”盜“成百上千的墓,但是極個(gè)別的墓則需要特殊手段了,目前市場(chǎng)上比較難以抓取的數(shù)據(jù)包括,微信公共賬號(hào),微博,facebook,ins,淘寶等等。具體原因各異,但基本無(wú)法用一個(gè)統(tǒng)一框架來(lái)完成,太特殊了。如果有一個(gè)通用的框架能解決我說(shuō)的這幾個(gè)網(wǎng)站的抓取,這一定是一個(gè)非常震撼的產(chǎn)品,如果有,一定要告訴我,那我公開(kāi)出來(lái),然后就改行了。
當(dāng)面對(duì)以上三個(gè)挑戰(zhàn)的時(shí)候,就需要智能爬蟲(chóng)。智能爬蟲(chóng)是讓爬蟲(chóng)的行為盡可能模仿人類(lèi)行為,讓反爬策略失效,只有”混在老百姓隊(duì)伍里面,才是安全的“,因此這就需要琢磨瀏覽器了,很多人把爬蟲(chóng)寫(xiě)在了瀏覽器插件里面,把爬蟲(chóng)寫(xiě)在了手機(jī)里面,寫(xiě)在了路由器里面(春節(jié)搶票王)。再有一個(gè)傳統(tǒng)的爬蟲(chóng)都是只有讀操作的,沒(méi)有寫(xiě)操作,這個(gè)很容易被判是爬蟲(chóng),智能的爬蟲(chóng)需要有一些自動(dòng)化交互的行為,這都是一些抵御反爬策略的方法。
1.3 爬蟲(chóng)基本原理
爬蟲(chóng)是模擬用戶(hù)在瀏覽器或者某個(gè)應(yīng)用上的操作,把操作的過(guò)程、實(shí)現(xiàn)自動(dòng)化的程序,當(dāng)我們?cè)跒g覽器中輸入一個(gè) url 后回車(chē),后臺(tái)會(huì)發(fā)生什么?比如說(shuō)你輸入http://www.sina.com.cn/,簡(jiǎn)單來(lái)說(shuō)這段過(guò)程發(fā)生了以下四個(gè)步驟:
- 1. 查找域名對(duì)應(yīng)的IP地址。
- 2. 向IP對(duì)應(yīng)的服務(wù)器發(fā)送請(qǐng)求。
- 3. 服務(wù)器響應(yīng)請(qǐng)求,發(fā)回網(wǎng)頁(yè)內(nèi)容。
- 4. 瀏覽器解析網(wǎng)頁(yè)內(nèi)容。
網(wǎng)絡(luò)爬蟲(chóng)本質(zhì):本質(zhì)就是瀏覽器http請(qǐng)求。瀏覽器和網(wǎng)絡(luò)爬蟲(chóng)是兩種不同的網(wǎng)絡(luò)客戶(hù)端,都以相同的方式來(lái)獲取網(wǎng)頁(yè)。
網(wǎng)絡(luò)爬蟲(chóng)要做的,簡(jiǎn)單來(lái)說(shuō),就是實(shí)現(xiàn)瀏覽器的功能。通過(guò)指定url,直接返回給用戶(hù)所需要的數(shù)據(jù), 而不需要一步步人工去操縱瀏覽器獲取。
瀏覽器是如何發(fā)送和接收這個(gè)數(shù)據(jù)呢?
- HTTP 簡(jiǎn)介:HTTP協(xié)議(HyperText Transfer Protocol,超文本傳輸協(xié)議)目的是為了提供一種發(fā)布和接收HTML(HyperText Markup Language)頁(yè)面的方法。
- HTTP 協(xié)議所在的協(xié)議層(了解):HTTP 是基于TCP協(xié)議之上的。在 TCP/IP 協(xié)議參考模型的各層對(duì)應(yīng)的協(xié)議如下圖,其中HTTP是應(yīng)用層的協(xié)議。 默認(rèn)HTTP的端口號(hào)為80,HTTPS的端口號(hào)為443。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 網(wǎng)絡(luò)模型圖
HTTP 工作過(guò)程
一次 HTTP 操作稱(chēng)為一個(gè)事務(wù),其工作整個(gè)過(guò)程如下:
- 1 ) 、地址解析。如用客戶(hù)端瀏覽器請(qǐng)求這個(gè)頁(yè)面:http://localhost.com:8080/index.htm從中分解出協(xié)議名、主機(jī)名、端口、對(duì)象路徑等部分,對(duì)于我們的這個(gè)地址,解析得到的結(jié)果如下: 協(xié)議名:http 主機(jī)名:localhost.com 端口:8080 對(duì)象路徑:/index.htm在這一步,需要域名系統(tǒng)DNS解析域名localhost.com,得主機(jī)的IP地址。
- 2)、封裝 HTTP 請(qǐng)求數(shù)據(jù)包。把以上部分結(jié)合本機(jī)自己的信息,封裝成一個(gè)HTTP請(qǐng)求數(shù)據(jù)包
- 3)封裝成 TCP 包,建立TCP連接(TCP的三次握手)。在HTTP工作開(kāi)始之前,客戶(hù)機(jī)(Web瀏覽器)首先要通過(guò)網(wǎng)絡(luò)與服務(wù)器建立連接,該連接是通過(guò)TCP來(lái)完成的,該協(xié)議與IP協(xié)議共同構(gòu)建Internet,即著名的TCP/IP協(xié)議族,因此Internet又被稱(chēng)作是TCP/IP網(wǎng)絡(luò)。HTTP是比TCP更高層次的應(yīng)用層協(xié)議,根據(jù)規(guī)則,只有低層協(xié)議建立之后才能,才能進(jìn)行更層協(xié)議的連接,因此,首先要建立TCP連接,一般TCP連接的端口號(hào)是80。這里是8080端口
- 4)客戶(hù)機(jī)發(fā)送請(qǐng)求命令。建立連接后,客戶(hù)機(jī)發(fā)送一個(gè)請(qǐng)求給服務(wù)器,請(qǐng)求方式的格式為:統(tǒng)一資源標(biāo)識(shí)符(URL)、協(xié)議版本號(hào),后邊是MIME信息包括請(qǐng)求修飾符、客戶(hù)機(jī)信息和可內(nèi)容。
- 5)服務(wù)器響應(yīng)。服務(wù)器接到請(qǐng)求后給予相應(yīng)的響應(yīng)信息,其格式為一個(gè)狀態(tài)行,包括信息的協(xié)議版本號(hào)、一個(gè)成功或錯(cuò)誤的代碼,后邊是 MIME 信息包括服務(wù)器信息、實(shí)體信息和可能的內(nèi)容。實(shí)體消息:服務(wù)器向?yàn)g覽器發(fā)送頭信息后,它會(huì)發(fā)送一個(gè)空白行來(lái)表示頭信息的發(fā)送到此為結(jié)束,接著,它就以Content-Type應(yīng)答頭信息所描述的格式發(fā)送用戶(hù)所請(qǐng)求的實(shí)際數(shù)據(jù)
- 6)服務(wù)器關(guān)閉 TCP 連接。一般情況下,一旦 Web 服務(wù)器向?yàn)g覽器發(fā)送了請(qǐng)求數(shù)據(jù),它就要關(guān)閉 TCP 連接,然后如果瀏覽器或者服務(wù)器在其頭信息加入了這行代碼 Connection:keep-alive 。TCP連接在發(fā)送后將仍然保持打開(kāi)狀態(tài),于是,瀏覽器可以繼續(xù)通過(guò)相同的連接發(fā)送請(qǐng)求。保持連接節(jié)省了為每個(gè)請(qǐng)求建立新連接所需的時(shí)間,還節(jié)約了網(wǎng)絡(luò)帶寬。
HTTP 協(xié)議棧數(shù)據(jù)流
HTTPS。HTTPS(全稱(chēng):Hypertext Transfer Protocol over Secure Socket Layer),是以安全為目標(biāo)的HTTP通道,簡(jiǎn)單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎(chǔ)是SSL。其所用的端口號(hào)是443。
SSL:安全套接層,是netscape公司設(shè)計(jì)的主要用于web的安全傳輸協(xié)議。這種協(xié)議在WEB上獲得了廣泛的應(yīng)用。通過(guò)證書(shū)認(rèn)證來(lái)確保客戶(hù)端和網(wǎng)站服務(wù)器之間的通信數(shù)據(jù)是加密安全的。
有兩種基本的加解密算法類(lèi)型:
- 1)對(duì)稱(chēng)加密(symmetrcic encryption):密鑰只有一個(gè),加密解密為同一個(gè)密碼,且加解密速度快,典型的對(duì)稱(chēng)加密算法有DES、AES,RC5,3DES等;對(duì)稱(chēng)加密主要問(wèn)題是共享秘鑰,除你的計(jì)算機(jī)(客戶(hù)端)知道另外一臺(tái)計(jì)算機(jī)(服務(wù)器)的私鑰秘鑰,否則無(wú)法對(duì)通信流進(jìn)行加密解密。解決這個(gè)問(wèn)題的方案非對(duì)稱(chēng)秘鑰。
- 2)非對(duì)稱(chēng)加密:使用兩個(gè)秘鑰:公共秘鑰和私有秘鑰。私有秘鑰由一方密碼保存(一般是服務(wù)器保存),另一方任何人都可以獲得公共秘鑰。這種密鑰成對(duì)出現(xiàn)(且根據(jù)公鑰無(wú)法推知私鑰,根據(jù)私鑰也無(wú)法推知公鑰),加密解密使用不同密鑰(公鑰加密需要私鑰解密,私鑰加密需要公鑰解密),相對(duì)對(duì)稱(chēng)加密速度較慢,典型的非對(duì)稱(chēng)加密算法有RSA、DSA等。
https 通信的優(yōu)點(diǎn):
客戶(hù)端產(chǎn)生的密鑰只有客戶(hù)端和服務(wù)器端能得到;
加密的數(shù)據(jù)只有客戶(hù)端和服務(wù)器端才能得到明文;
客戶(hù)端到服務(wù)端的通信是安全的。
1.4 爬蟲(chóng)工作流程
網(wǎng)絡(luò)爬蟲(chóng)的基本工作流程如下:
- 1. 首先選取一部分精心挑選的種子 URL;
- 2. 將這些 URL 放入待抓取 URL 隊(duì)列;
- 3. 從待抓取 URL 隊(duì)列中取出待抓取在 URL,解析 DNS,并且得到主機(jī)的 ip,將 URL 對(duì)應(yīng)的網(wǎng)頁(yè)下載下來(lái)并存儲(chǔ)到已下載網(wǎng)頁(yè)庫(kù)中。此外,將這些 URL 放進(jìn)已抓取 URL 隊(duì)列。
- 4. 分析已抓取 URL 隊(duì)列中的 URL,分析其中的其他 URL,并且將 URL 放入待抓取 URL 隊(duì)列,從而進(jìn)入下一個(gè)循環(huán)。
?;ňW(wǎng) 爬取 示例:爬取 大學(xué)校花(?http://www.521609.com/daxuexiaohua/ )
這個(gè)爬蟲(chóng)只是爬取一個(gè) URL,并沒(méi)有提取更多 URL 進(jìn)行爬取
# -*- 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 不但能截獲各種瀏覽器發(fā)出的HTTP請(qǐng)求, 也可以截獲各種智能手機(jī)發(fā)出的 HTTP/HTTPS請(qǐng)求。 Fiddler 能捕獲 IOS 設(shè)備發(fā)出的請(qǐng)求,比如 IPhone, IPad, MacBook. 等等蘋(píng)果的設(shè)備。 同理,也可以截獲 Andriod,Windows Phone 的等設(shè)備發(fā)出的HTTP/HTTPS。工作原理:Fiddler 是以代理 web 服務(wù)器的形式工作的,它使用代理地址:127.0.0.1,端口:8888。
Fiddler 抓取 HTTPS 設(shè)置:啟動(dòng) Fiddler,打開(kāi)菜單欄中的 Tools > Fiddler Options,打開(kāi) “Fiddler Options” 對(duì)話(huà)框。
選中 Capture HTTPS CONNECTs,再選中下方 Ignore server certificate errors,
然后再點(diǎn)擊 Actions 安裝證書(shū)( 要抓取 HTTPS 的流量,必須安裝證書(shū) ),安裝為 "根證書(shū)"。。。
配置 Fiddler 允許遠(yuǎn)程連接( 可以抓取手機(jī)流量?):
?Connections 頁(yè)簽,選中 Allow remote computers to connect。重啟 Fidler(這一步很重要,必須做)。
Fiddler 如何捕獲 Chrome的會(huì)話(huà):switchyomega 安裝插件?
百度( 百度的時(shí)候把點(diǎn)去掉?):i點(diǎn)sha點(diǎn)dow點(diǎn)socks? 或者? lan點(diǎn)tern
打開(kāi)網(wǎng)址 chrome 網(wǎng)上應(yīng)用商店,然后搜索 "switchyomega"。
Fiddler 如何捕獲 Firefox 的會(huì)話(huà)
能支持 HTTP 代理的任意程序的數(shù)據(jù)包都能被 Fiddler 嗅探到,Fiddler 的運(yùn)行機(jī)制其實(shí)就是本機(jī)上監(jiān)聽(tīng) 8888 端口的 HTTP代理。 Fiddler2啟動(dòng)的時(shí)候默認(rèn)IE的代理設(shè)為了127.0.0.1:8888,而其他瀏覽器是需要手動(dòng)設(shè)置的,所以將Firefox的代理改為127.0.0.1:8888就可以監(jiān)聽(tīng)數(shù)據(jù)了。 Firefox 上通過(guò)如下步驟設(shè)置代理 點(diǎn)擊: Tools -> Options, 在Options 對(duì)話(huà)框上點(diǎn)擊Advanced tab - > network tab -> setting.
Fiddler 的基本界面
特別注意: 遇到這個(gè) Click 請(qǐng)點(diǎn)擊 Click?
Fiddler 強(qiáng)大的 Script 系統(tǒng)
Fiddler 包含了一個(gè)強(qiáng)大的基于事件腳本的子系統(tǒng),并且能使用 .net 語(yǔ)言進(jìn)行擴(kuò)展。?
官方的幫助文檔:?http://www.fiddler2.com/Fiddler/dev/ScriptSamples.asp
Fiddler 的?Fiddler Script 標(biāo)簽,如下圖:
在里面我們就可以編寫(xiě)腳本了, 看個(gè)實(shí)例讓所有 cnblogs 的會(huì)話(huà)都顯示紅色。 把這段腳本放在 OnBeforeRequest(oSession: Session) 方法下,并且點(diǎn)擊 "Save script"
if (oSession.HostnameIs("www.cnblogs.com")) {oSession["ui-color"] = "red"; }這樣所有的cnblogs的會(huì)話(huà)都會(huì)顯示紅色。
1.6 HTTP 協(xié)議介紹
HTTP (HyperText Transfer Protocol) 提供了一種發(fā)布和接收HTML(HyperText Markup Language)頁(yè)面的方法。
Http 兩部分組成:請(qǐng)求、響應(yīng)。
客戶(hù)端請(qǐng)求消息:客戶(hù)端發(fā)送一個(gè)HTTP請(qǐng)求到服務(wù)器的請(qǐng)求消息包括以下格式:請(qǐng)求行(request line)、請(qǐng)求頭部(header)、空行和請(qǐng)求數(shù)據(jù)四個(gè)部分組成,下圖給出了請(qǐng)求報(bào)文的一般格式。
服務(wù)器響應(yīng)消息:HTTP 響應(yīng)也由四個(gè)部分組成,分別是:狀態(tài)行、消息報(bào)頭、空行 和 響應(yīng)正文。
cookies 和 session
服務(wù)器 和 客戶(hù)端 的交互僅限于請(qǐng)求/響應(yīng)過(guò)程,結(jié)束之后便斷開(kāi), 在下一次請(qǐng)求服務(wù)器會(huì)認(rèn)為新的客戶(hù)端。為了維護(hù)他們之間的鏈接,讓服務(wù)器知道這是前一個(gè)用戶(hù)發(fā)送的請(qǐng)求,必須在一個(gè)地方保存客戶(hù)端的信息:
- Cookie 通過(guò)在客戶(hù)端記錄信息確定用戶(hù)身份。
- Session 通過(guò)在服務(wù)器端記錄信息確定用戶(hù)身份。
HTTP 請(qǐng)求
請(qǐng)求方法
根據(jù) HTTP 標(biāo)準(zhǔn),HTTP 請(qǐng)求可以使用多種請(qǐng)求方法。
HTTP 1.0 定義了三種請(qǐng)求方法: GET,POST 和 HEAD方法。
HTTP 1.1 新增了五種請(qǐng)求方法:OPTIONS,PUT,DELETE,TRACE 和 CONNECT 方法。
| 序號(hào) | 方法 | 描述 |
| 1 | GET | 請(qǐng)求指定的頁(yè)面信息,并返回實(shí)體主體。 |
| 2 | HEAD | 類(lèi)似于 get 請(qǐng)求,只不過(guò)返回的響應(yīng)中沒(méi)有具體的內(nèi)容,用于獲取報(bào)頭 |
| 3 | POST | 向指定資源提交數(shù)據(jù)進(jìn)行處理請(qǐng)求(例如提交表單或者上傳文件)。數(shù)據(jù)被包含在請(qǐng)求體中。 POST 請(qǐng)求可能會(huì)導(dǎo)致新的資源的建立和/或已有資源的修改。 |
| 4 | PUT | 從客戶(hù)端向服務(wù)器傳送的數(shù)據(jù)取代指定的文檔的內(nèi)容。 |
| 5 | DELETE | 請(qǐng)求服務(wù)器刪除指定的頁(yè)面。 |
| 6 | CONNECT | HTTP/1.1 協(xié)議中預(yù)留給能夠?qū)⑦B接改為管道方式的代理服務(wù)器。 |
| 7 | OPTIONS | 允許客戶(hù)端查看服務(wù)器的性能。 |
| 8 | TRACE | 回顯服務(wù)器收到的請(qǐng)求,主要用于測(cè)試或診斷。 |
GET和POST方法區(qū)別歸納如下幾點(diǎn):
- 1. GET 是從服務(wù)器上獲取數(shù)據(jù),POST 是向服務(wù)器傳送數(shù)據(jù)。
- 2. GET 請(qǐng)求的時(shí)候,參數(shù)都顯示在瀏覽器網(wǎng)址上。POST 請(qǐng)求的時(shí)候,參數(shù)在請(qǐng)求體當(dāng)中,消息長(zhǎng)度沒(méi)有限制而且以隱式的方式進(jìn)行發(fā)送
- 3. 盡量避免使用 Get 方式提交表單,因?yàn)橛锌赡軙?huì)導(dǎo)致安全問(wèn)題。 比如說(shuō)在登陸表單中用 Get 方式,用戶(hù)輸入的用戶(hù)名和密碼將在地址欄中暴露無(wú)遺。 但是在分頁(yè)程序中用 Get 方式就比用 Post 好。
URL概述
統(tǒng)一資源定位符(URL,英語(yǔ) Uniform / Universal Resource Locator的縮寫(xiě))是用于完整地描述 Internet 上網(wǎng)頁(yè)和其他資源的地址的一種標(biāo)識(shí)方法。
URL 格式:基本格式如下 schema://host[:port#]/path/…/[?query-string][#anchor]
- 1. schema 協(xié)議 (例如:http, https, ftp)
- 2. host 服務(wù)器的IP地址或者域名
- 3. port# 服務(wù)器的端口(如果是走協(xié)議默認(rèn)端口,缺省端口80)
- 4. path 訪問(wèn)資源的路徑
- 5. query-string 參數(shù),發(fā)送給http服務(wù)器的數(shù)據(jù)
- 6. anchor- 錨(跳轉(zhuǎn)到網(wǎng)頁(yè)的指定錨點(diǎn)位置)
例子:
URL 和 URI 的區(qū)別
- URL:統(tǒng)一資源定位符(uniform resource location);平時(shí)上網(wǎng)時(shí)在 IE 瀏覽器中輸入的那個(gè)地址就是 URL。比如:網(wǎng)易 http://www.163.com 就是一個(gè)URL 。URL 是 Internet上用來(lái)描述信息資源的字符串,主要用在各種 WWW 客戶(hù)程序和服務(wù)器程序上。采用 URL 可以用一種統(tǒng)一的格式來(lái)描述各種信息資源,包括文件、服務(wù)器的地址和目錄等。
- URI:統(tǒng)一資源標(biāo)識(shí)符(uniform resource identifier)。Web 上可用的每種資源 - HTML 文檔、圖像、視頻片段、程序, 由一個(gè)通過(guò)通用資源標(biāo)志符 (Universal Resource Identifier, 簡(jiǎn)稱(chēng) "URI") 進(jìn)行定位。URI 是個(gè)純粹的語(yǔ)法結(jié)構(gòu),用于指定標(biāo)識(shí) web資源的字符串的各個(gè)不同部分,URL 是 URI 的一個(gè)特例,它包含定位 web 資源的足夠信息。
- URL 是 URI 的一個(gè)子集
一個(gè) URL 的請(qǐng)求過(guò)程:
- 當(dāng)你在瀏覽器輸入U(xiǎn)RL?https://www.baidu.com/ 的時(shí)候,瀏覽器發(fā)送一個(gè)Request 去獲取?https://www.baidu.com/ 的 html。
- 服務(wù)器把 Response 發(fā)送回給瀏覽器.
- 瀏覽器分析 Response 中的 HTML,發(fā)現(xiàn)其中引用了很多其他文件,比如:圖片,CSS文件,JS文件。
- 瀏覽器會(huì)自動(dòng)再次發(fā)送 Request 去獲取圖片,CSS文件,或者JS文件。
- 當(dāng)所有的文件都下載成功后, 網(wǎng)頁(yè)就被顯示出來(lái)了。
常用的請(qǐng)求報(bào)頭
- Host:Host初始URL中的主機(jī)和端口,用于指定被請(qǐng)求資源的Internet主機(jī)和端口號(hào),它通常從HTTP URL中提取出來(lái)的
- Connection:表示客戶(hù)端與服務(wù)連接類(lèi)型;
1. client 發(fā)起一個(gè)包含 Connection:keep-alive 的請(qǐng)求
2. server 收到請(qǐng)求后,如果 server 支持 keepalive 回復(fù)一個(gè)包含Connection:keep-alive的
? ?響應(yīng)不關(guān)閉連接,否則回復(fù)一個(gè)包含Connection:close的響應(yīng)關(guān)閉連接。
3. 如果 client 收到包含 Connection:keep-alive 的響應(yīng),向同一個(gè)連接發(fā)送下一個(gè)請(qǐng)求,
? ?直到一方主動(dòng)關(guān)閉連接。 Keep-alive在很多情況下能夠重用連接,減少資源消耗,縮短響應(yīng)時(shí)間HTTP - Accept:表示瀏覽器支持的 MIME 類(lèi)型
MIME 的英文全稱(chēng)是 Multipurpose Internet Mail Extensions(多用途互聯(lián)網(wǎng)郵件擴(kuò)展)
eg:
Accept:image/gif,表明客戶(hù)端希望接受GIF圖象格式的資源;
Accept:text/html,表明客戶(hù)端希望接受html文本。
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
意思:瀏覽器支持的 MIME 類(lèi)型分別是 text/html、application/xhtml+xml、application/xml 和 */*,
? ? ? 優(yōu)先順序是它們從左到右的排列順序。
Text:用于標(biāo)準(zhǔn)化地表示的文本信息,文本消息可以是多種字符集和或者多種格式的;
Application:用于傳輸應(yīng)用程序數(shù)據(jù)或者二進(jìn)制數(shù)據(jù);設(shè)定某種擴(kuò)展名的文件用一種應(yīng)用程序來(lái)
? ? ? ? ? ? ?打開(kāi)的方式類(lèi)型,當(dāng)該擴(kuò)展名文件被訪問(wèn)的時(shí)候,瀏覽器會(huì)自動(dòng)使用指定應(yīng)用程序來(lái)打開(kāi)
q 是權(quán)重系數(shù),范圍 0 =< q <= 1,q 值越大,請(qǐng)求越傾向于獲得其“;”之前的類(lèi)型表示的內(nèi)容,
若沒(méi)有指定 q 值越大,請(qǐng)求越傾向于獲得其“,則默認(rèn)為1,
若被賦值為0,則用于提醒服務(wù)器哪些是瀏覽器不接受的內(nèi)容類(lèi)型。
| Mime類(lèi)型 | 擴(kuò)展名 |
| text/html | .htm?.html *.shtml |
| text/plain | text/html是以html的形式輸出,比如<input type="text"/>就會(huì)在頁(yè)面上顯示一個(gè)文本框,而以plain形式就會(huì)在頁(yè)面上原樣顯示這段代碼 |
| 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 提交的數(shù)據(jù)按照 key1=val1&key2=val2 的方式進(jìn)行編碼,key 和 val 都進(jìn)行了 URL 轉(zhuǎn)碼。
- User-Agent: 瀏覽器類(lèi)型
- Referer: 請(qǐng)求來(lái)自哪個(gè)頁(yè)面,用戶(hù)是從該 Referer URL頁(yè)面訪問(wèn)當(dāng)前請(qǐng)求的頁(yè)面。
- Accept-Encoding:瀏覽器支持的壓縮編碼類(lèi)型,比如gzip,支持gzip的瀏覽器返回經(jīng)gzip編碼的HTML頁(yè)面。許多情形下這可以減少5到10倍的下載時(shí)間
eg:
Accept-Encoding:gzip;q=1.0, identity; q=0.5, *;q=0 // 按順序支持 gzip , identity
如果有多個(gè)Encoding同時(shí)匹配, 按照q值順序排列
如果請(qǐng)求消息中沒(méi)有設(shè)置這個(gè)域,服務(wù)器假定客戶(hù)端對(duì)各種內(nèi)容編碼都可以接受。
- Accept-Language:瀏覽器所希望的語(yǔ)言種類(lèi),當(dāng)服務(wù)器能夠提供一種以上的語(yǔ)言版本時(shí)要用到。
eg:
Accept-Language:zh-cn
如果請(qǐng)求消息中沒(méi)有設(shè)置這個(gè)報(bào)頭域,服務(wù)器假定客戶(hù)端對(duì)各種語(yǔ)言都可以接受。 - Accept-Charset:瀏覽器可接受的字符集,用于指定客戶(hù)端接受的字符集
eg:
Accept-Charset:iso-8859-1,gb2312
ISO8859-1,通常叫做Latin-1。Latin-1包括了書(shū)寫(xiě)所有西方歐洲語(yǔ)言不可缺少的附加字符;
gb2312是標(biāo)準(zhǔn)中文字符集;
UTF-8 是 UNICODE 的一種變長(zhǎng)字符編碼,可以解決多種語(yǔ)言文本顯示問(wèn)題,從而實(shí)現(xiàn)應(yīng)用國(guó)際化和本地化。
如果在請(qǐng)求消息中沒(méi)有設(shè)置這個(gè)域,缺省是任何字符集都可以接受。
HTTP 響應(yīng)
服務(wù)器上每一個(gè)HTTP 應(yīng)答對(duì)象 response 都包含一個(gè)數(shù)字 "狀態(tài)碼"。HTTP 狀態(tài)碼表示 HTTP 協(xié)議所返回的響應(yīng)的狀態(tài)。
比如:客戶(hù)端向服務(wù)器發(fā)送請(qǐng)求,如果成功地獲得請(qǐng)求的資源,則返回的狀態(tài)碼為200,表示響應(yīng)成功。如果請(qǐng)求的資源不存在, 則通常返回404錯(cuò)誤。
HTTP?響應(yīng)狀態(tài)碼通常分為5種類(lèi)型,分別以1~5五個(gè)數(shù)字開(kāi)頭,由3位整數(shù)組成,第一個(gè)數(shù)字定義了響應(yīng)的類(lèi)別:
| 分類(lèi) | 分類(lèi)描述 |
| 1** | 信息,服務(wù)器收到請(qǐng)求,需要請(qǐng)求者繼續(xù)執(zhí)行操作 |
| 2** | 成功,操作被成功接收并處理 |
| 3** | 重定向,需要進(jìn)一步的操作以完成請(qǐng)求 |
| 4** | 客戶(hù)端錯(cuò)誤,請(qǐng)求包含語(yǔ)法錯(cuò)誤或無(wú)法完成請(qǐng)求 |
| 5** | 服務(wù)器錯(cuò)誤,服務(wù)器在處理請(qǐng)求的過(guò)程中發(fā)生了錯(cuò)誤 |
最常用的響應(yīng)狀態(tài)碼
200 (OK): 請(qǐng)求成功,找到了該資源,并且一切正常。處理方式:獲得響應(yīng)的內(nèi)容,進(jìn)行處理?
201 請(qǐng)求完成,結(jié)果是創(chuàng)建了新資源。新創(chuàng)建資源的URI可在響應(yīng)的實(shí)體中得到 ? ?處理方式:爬蟲(chóng)中不會(huì)遇到?
202 請(qǐng)求被接受,但處理尚未完成 ? ?處理方式:阻塞等待?
204 服務(wù)器端已經(jīng)實(shí)現(xiàn)了請(qǐng)求,但是沒(méi)有返回新的信 息。如果客戶(hù)是用戶(hù)代理,則無(wú)須為此更新自身的文檔視圖。 ? ?處理方式:丟棄
300 該狀態(tài)碼不被HTTP/1.0的應(yīng)用程序直接使用, 只是作為3XX類(lèi)型回應(yīng)的默認(rèn)解釋。存在多個(gè)可用的被請(qǐng)求資源。 ? ?處理方式:若程序中能夠處理,則進(jìn)行進(jìn)一步處理,如果程序中不能處理,則丟棄
301 (Moved Permanently): 請(qǐng)求的文檔在其他地方,新的URL在Location頭中給出,瀏覽器應(yīng)該自動(dòng)地訪問(wèn)新的URL。處理方式:重定向到分配的URL
302 (Found): 類(lèi)似于301,但新的URL應(yīng)該被視為臨時(shí)性的替代,而不是永久性的。處理方式:重定向到臨時(shí)的URL?
304 (NOT MODIFIED): 該資源在上次請(qǐng)求之后沒(méi)有任何修改。這通常用于瀏覽器的緩存機(jī)制。處理方式:丟棄?
400 (Bad Request): 請(qǐng)求出現(xiàn)語(yǔ)法錯(cuò)誤。非法請(qǐng)求 ? ? 處理方式:丟棄?
401 未授權(quán) ? ? 處理方式:丟棄?
403 (FORBIDDEN): 客戶(hù)端未能獲得授權(quán)。這通常是在401之后輸入了不正確的用戶(hù)名或密碼。禁止訪問(wèn) ? ?處理方式:丟棄?
404 (NOT FOUND): 在指定的位置不存在所申請(qǐng)的資源。沒(méi)有找到 。 ? ?處理方式:丟棄?
5XX 回應(yīng)代碼以“5”開(kāi)頭的狀態(tài)碼表示服務(wù)器端發(fā)現(xiàn)自己出現(xiàn)錯(cuò)誤,不能繼續(xù)執(zhí)行請(qǐng)求 ? ?處理方式:丟棄
500 (Internal Server Error): 服務(wù)器遇到了意料不到的情況,不能完成客戶(hù)的請(qǐng)求
503 (Service Unavailable): 服務(wù)器由于維護(hù)或者負(fù)載過(guò)重未能應(yīng)答。
例如,Servlet可能在數(shù)據(jù)庫(kù)連接池已滿(mǎn)的情況下返回503。服務(wù)器返回503時(shí)可以提供一個(gè)Retry-After頭
常用的響應(yīng)報(bào)頭(了解)
Location:表示客戶(hù)應(yīng)當(dāng)?shù)侥睦锶ヌ崛∥臋n,用于重定向接受者到一個(gè)新的位置
Server:服務(wù)器名字,包含了服務(wù)器用來(lái)處理請(qǐng)求的軟件信息
eg: Server響應(yīng)報(bào)頭域的一個(gè)例子:
Server:Apache-Coyote/1.1
Set-Cookie:設(shè)置和頁(yè)面關(guān)聯(lián)的Cookie。
例如:前一個(gè) cookie 被存入瀏覽器并且瀏覽器試圖請(qǐng)求 http://www.ibm.com/foo/index.html 時(shí)
Set-Cookie:customer=huangxp; path=/foo; domain=.ibm.com;?
expires= Wednesday, 19-OCT-05 23:12:40 GMT;
Set-Cookie的每個(gè)屬性解釋如下:
Customer=huangxp 一個(gè)"名稱(chēng)=值"對(duì),把名稱(chēng)customer設(shè)置為值"huangxp",這個(gè)屬性在Cookie中必須有。
path=/foo 服務(wù)器路徑。
domain=.ibm.com 指定cookie 的域名。
expires= Wednesday, 19-OCT-05 23:12:40 GMT 指定cookie 失效的時(shí)間
POST 請(qǐng)求方式
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')) # 轉(zhuǎn)為utf-8,如果出現(xiàn)異常,責(zé)忽略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請(qǐng)求時(shí)會(huì)發(fā)現(xiàn)存在異常情況,常用的請(qǐng)求異常類(lèi)是 HTTPError/URLError 兩種,HTTPError異常時(shí)URLError異常的子類(lèi),是帶異常狀態(tài)碼和異常原因的,而URLError異常類(lèi)是不帶狀態(tài)碼的,所以在使用中不用直接用URLError代替HTTPError異常,如果要代替,一定要判斷是否有狀態(tài)碼屬性URLError:1/連接不上遠(yuǎn)程服務(wù)器;2/url不存在;3/本地沒(méi)有網(wǎng)絡(luò);4/假如觸發(fā) http error所以通常用特殊處理來(lái)使用URLError來(lái)替代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_爬蟲(chóng)
# -*- 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': '爬蟲(chóng)'}# 注意:對(duì)于需要傳入的 data 數(shù)據(jù),需要進(jìn)行 urlencode 編碼。form_data = parse.urlencode(form_data).encode('utf-8')print(f'運(yùn)行到第 ({page}) 頁(yè)面')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 出現(xiàn) 'ascii' codec can't encode characters問(wèn)題# 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)提出一個(gè)問(wèn)題,如果要采集的是 拉鉤招聘網(wǎng)站 北京>>朝陽(yáng)區(qū)>>望京 以這個(gè)網(wǎng)站為例,該如何理解這個(gè)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': '朝陽(yáng)區(qū)','bizArea': '望京' } print(parse.urlencode(query)) page = 3 values = {'first': 'false','pn': str(page),'kd': '后端開(kāi)發(fā)', } form_data = parse.urlencode(values) print(form_data)小結(jié)
- Content-Length: 是指報(bào)頭Header以外的內(nèi)容長(zhǎng)度,指 表單數(shù)據(jù)長(zhǎng)度
- X-Requested-With: XMLHttpRequest :表示Ajax異步請(qǐng)求
- Content-Type: application/x-www-form-urlencoded :表示:提交的表單數(shù)據(jù) 會(huì)按照name/value 值對(duì) 形式進(jìn)行編碼。例如:name1=value1&name2=value2... 。name 和 value 都進(jìn)行了 URL 編碼(utf-8、gb2312)
2. 爬蟲(chóng)入門(mén) 基礎(chǔ)篇
| 數(shù)據(jù)格式 | 描述 | 設(shè)計(jì)目標(biāo) |
| XML | Extensible Markup Language (可擴(kuò)展標(biāo)記語(yǔ)言) | 被設(shè)計(jì)為傳輸和存儲(chǔ)數(shù)據(jù),其焦點(diǎn)是數(shù)據(jù)的內(nèi)容 |
| HTML | HyperText Markup Language(超文本標(biāo)記語(yǔ)言) | 顯示數(shù)據(jù)以及如何更好顯示數(shù)據(jù) |
| HTML DOM | HTML Document Object Model(文檔對(duì)象模型) | 通過(guò) JavaScript,您可以重構(gòu)整個(gè)HTML文檔。您可以添加、移除、改變或重排頁(yè)面上的項(xiàng)目。要改變頁(yè)面的某個(gè)東西,JavaScript就需要對(duì)HTML文檔中所有元素進(jìn)行訪問(wèn)的入口。 |
XML
XML DOM 定義訪問(wèn)和操作 XML 文檔的標(biāo)準(zhǔn)方法。 DOM 將 XML 文檔作為一個(gè)樹(shù)形結(jié)構(gòu),而樹(shù)葉被定義為節(jié)點(diǎn)。
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 定義了訪問(wèn)和操作 HTML 文檔的標(biāo)準(zhǔn)方法。 DOM 以樹(shù)結(jié)構(gòu)表達(dá) HTML 文檔。
2.1 頁(yè)面解析之?dāng)?shù)據(jù)提取
抓取網(wǎng)站或者某個(gè)應(yīng)用的數(shù)據(jù)一般分為兩部分,非結(jié)構(gòu)化的文本,或結(jié)構(gòu)化的文本。
- 結(jié)構(gòu)化的數(shù)據(jù):JSON、XML
- 非結(jié)構(gòu)化的數(shù)據(jù):HTML文本(包含JavaScript代碼)
HTML文本(包含JavaScript代碼)是最常見(jiàn)的數(shù)據(jù)格式,理應(yīng)屬于結(jié)構(gòu)化的文本組織,但因?yàn)橐话阄覀冃枰年P(guān)鍵信息并非直接可以得到,需要進(jìn)行對(duì)HTML的解析查找,甚至一些字符串操作才能得到,所以還是歸類(lèi)于非結(jié)構(gòu)化的數(shù)據(jù)處理中。
把網(wǎng)頁(yè)比作一個(gè)人,那么 HTML 便是他的骨架,JS 便是他的肌肉,CSS 便是它的衣服。常見(jiàn)解析方式如下:
- XPath
- CSS選擇器
- 正則表達(dá)式
一段文本,例如一篇文章,或者一句話(huà),我們的初衷是提取有效信息,所以如果是滯后處理,可以直接存儲(chǔ),如果是需要實(shí)時(shí)提取有用信息,常見(jiàn)的處理方式如下:
- 分詞,根據(jù)抓取的網(wǎng)站類(lèi)型,使用不同詞庫(kù),進(jìn)行基本的分詞,然后變成詞頻統(tǒng)計(jì)
- NLP 自然語(yǔ)言處理,進(jìn)行語(yǔ)義分析,用結(jié)果表示,例如正負(fù)面等。
2.2 非結(jié)構(gòu)化數(shù)據(jù)之 XPath
XPath 語(yǔ)言:XPath(XML Path Language)是 XML 路徑語(yǔ)言,它是一種用來(lái)定位 XML 文檔中某部分位置的語(yǔ)言。將 HTML 轉(zhuǎn)換成 XML 文檔之后,用 XPath 查找 HTML 節(jié)點(diǎn)或元素。比如:用 "/" 來(lái)作為上下層級(jí)間的分隔,第一個(gè) "/" 表示文檔的根節(jié)點(diǎn)(注意,不是指文檔最外層的 tag 節(jié)點(diǎn),而是指文檔本身),對(duì)于一個(gè) HTML 文件來(lái)說(shuō),最外層的節(jié)點(diǎn)應(yīng)該是 "/html" 。
XPath開(kāi)發(fā)工具:開(kāi)源的 XPath 表達(dá)式編輯工具:XMLQuire(XML格式文件可用)、chrome 插件 XPath Helper
firefox插件 XPath Checker
XPath語(yǔ)法 (?XPath語(yǔ)法參考文檔:http://www.w3school.com.cn/xpath/index.asp )
XPath 是一門(mén)在 XML 文檔中查找信息的語(yǔ)言。
XPath 可用來(lái)在 XML 文檔中對(duì)元素和屬性進(jìn)行遍歷。
選取節(jié)點(diǎn) XPath 使用路徑表達(dá)式在 XML 文檔中選取節(jié)點(diǎn)。節(jié)點(diǎn)是通過(guò)沿著路徑或者 step 來(lái)選取的。
下面列出了最有用的路徑表達(dá)式:
| 表達(dá)式 | 描述 |
| / | 從根節(jié)點(diǎn)選取。 |
| nodename | 選取此節(jié)點(diǎn)的所有子節(jié)點(diǎn)。 |
| // | 從當(dāng)前節(jié)點(diǎn) 選擇?所有匹配文檔中的節(jié)點(diǎn) |
| . | 選取當(dāng)前節(jié)點(diǎn)。 |
| .. | 選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)。 |
| @ | 選取屬性。 |
在下面的表格中,我們已列出了一些路徑表達(dá)式以及表達(dá)式的結(jié)果:
| 路徑表達(dá)式 | 結(jié)果 |
| /bookstore | 選取根元素 bookstore。注釋:假如路徑起始于正斜杠( / ),則此路徑始終代表到某元素的絕對(duì)路徑! |
| bookstore | 選取 bookstore 元素的所有子節(jié)點(diǎn)。默認(rèn)從根節(jié)點(diǎn)選取 |
| bookstore/book | 選取屬于 bookstore 的子元素的所有 book 元素。 |
| //book | 選取所有 book 子元素,而不管它們?cè)谖臋n中的位置。 |
| //book/./title | 選取所有 book 子元素,從當(dāng)前節(jié)點(diǎn)查找title節(jié)點(diǎn) |
| //price/.. | 選取所有 book 子元素,從當(dāng)前節(jié)點(diǎn)查找父節(jié)點(diǎn) |
| bookstore//book | 選擇屬于 bookstore 元素的后代的所有 book 元素,而不管它們位于 bookstore 之下的什么位置。 |
| //@lang | 選取名為 lang 的所有屬性。 |
謂語(yǔ)條件(Predicates)
謂語(yǔ)用來(lái)查找某個(gè)特定的信息或者包含某個(gè)指定的值的節(jié)點(diǎn)。
所謂"謂語(yǔ)條件",就是對(duì)路徑表達(dá)式的附加條件
謂語(yǔ)是被嵌在方括號(hào)中,都寫(xiě)在方括號(hào)"[]"中,表示對(duì)節(jié)點(diǎn)進(jìn)行進(jìn)一步的篩選。
在下面的表格中,我們列出了帶有謂語(yǔ)的一些路徑表達(dá)式,以及表達(dá)式的結(jié)果:
| 路徑表達(dá)式 | 結(jié)果 |
| /bookstore/book[1] | 選取屬于 bookstore 子元素的第一個(gè) book 元素。 |
| /bookstore/book[last()] | 選取屬于 bookstore 子元素的最后一個(gè) book 元素。 |
| /bookstore/book[last()-1] | 選取屬于 bookstore 子元素的倒數(shù)第二個(gè) book 元素。 |
| /bookstore/book[position()<3] | 選取最前面的兩個(gè)屬于 bookstore 元素的子元素的 book 元素。 |
| //title[@lang] | 選取所有擁有名為 lang 的屬性的 title 元素。 |
| //title[@lang=’eng’] | 選取所有 title 元素,且這些元素?fù)碛兄禐?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 |
選取未知節(jié)點(diǎn):XPath 通配符可用來(lái)選取未知的 XML 元素。
| 通配符 | 描述 |
| * | 匹配任何元素節(jié)點(diǎn)。 |
| @* | 匹配任何屬性節(jié)點(diǎn)。 |
在下面的表格中,我們列出了一些路徑表達(dá)式,以及這些表達(dá)式的結(jié)果:
| 路徑表達(dá)式 | 結(jié)果 |
| /bookstore/* | 選取 bookstore 元素的所有子元素。 |
| //* | 選取文檔中的所有元素。 |
| //title[@*] | 選取所有帶有屬性的 title 元素。 |
選取若干路徑: 通過(guò)在路徑表達(dá)式中使用“|”運(yùn)算符,您可以選取若干個(gè)路徑。
在下面的表格中,我們列出了一些路徑表達(dá)式,以及這些表達(dá)式的結(jié)果:
| 路徑表達(dá)式 | 結(jié)果 |
| //book/title | //book/price | 選取 book 元素的所有 title 和 price 元素。 |
| //title | //price | 選取文檔中的所有 title 和 price 元素。 |
| /bookstore/book/title | //price | 選取屬于 bookstore 元素的 book 元素的所有 title 元素,以及文檔中所有的 price 元素。 |
XPath 高級(jí)用法
模糊查詢(xún) contains
目前許多 web 框架,都是動(dòng)態(tài)生成界面的元素id,因此在每次操作相同界面時(shí),ID都是變化的,這樣為自動(dòng)化測(cè)試造成了一定的影響。
<div class="eleWrapper" title="請(qǐng)輸入用戶(hù)名"> <input type="text" class="textfield" name="ID9sLJQnkQyLGLhYShhlJ6gPzHLgvhpKpLzp2Tyh4hyb1b4pnvzxFR!-166749344!1357374592067" id="nt1357374592068" /> </div>解決方法 使用 xpath 的匹配功能,//input[contains(@id,'nt')]
測(cè)試使用的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.查詢(xún)所有Blog節(jié)點(diǎn)值中帶有 cn 字符串的Person節(jié)點(diǎn)。Xpath表達(dá)式:/Root//Person[contains(Blog,'cn')]
2.查詢(xún)所有Blog節(jié)點(diǎn)值中帶有 cn 字符串并且屬性ID值中有01的Person節(jié)點(diǎn)。Xpath表達(dá)式:/Root//Person[contains(Blog,'cn') and contains(@ID,'01')]
xpath 學(xué)習(xí)筆記
1.依靠自己的屬性,文本定位//td[text()='Data Import']//div[contains(@class,'cux-rightArrowIcon-on')]//a[text()='馬上注冊(cè)']//input[@type='radio' and @value='1'] 多條件//span[@name='bruce'][text()='bruce1'][1] 多條件//span[@id='bruce1' or text()='bruce2'] 找出多個(gè)//span[text()='bruce1' and text()='bruce2'] 找出多個(gè) 2.依靠父節(jié)點(diǎn)定位//div[@class='x-grid-col-name x-grid-cell-inner']/div//div[@id='dynamicGridTestInstanceformclearuxformdiv']/div//div[@id='test']/input 3.依靠子節(jié)點(diǎn)定位//div[div[@id='navigation']]//div[div[@name='listType']]//div[p[@name='testname']] 4.混合型//div[div[@name='listType']]//img//td[a//font[contains(text(),'seleleium2從零開(kāi)始 視屏')]]//input[@type='checkbox'] 5.進(jìn)階部分//input[@id='123']/following-sibling::input 找下一個(gè)兄弟節(jié)點(diǎn)//input[@id='123']/preceding-sibling::span 上一個(gè)兄弟節(jié)點(diǎn)//input[starts-with(@id,'123')] 以什么開(kāi)頭//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,取長(zhǎng)度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 找父節(jié)點(diǎn)//div[span[text()='+++current node']]/ancestor::div 找祖先節(jié)點(diǎn) 10.孫子節(jié)點(diǎn)//div[span[text()='current note']]/descendant::div/span[text()='123']//div[span[text()='current note']]//div/span[text()='123'] 兩個(gè)表達(dá)的意思一樣 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提取多個(gè)標(biāo)簽下的text在寫(xiě)爬蟲(chóng)的時(shí)候,經(jīng)常會(huì)使用xpath進(jìn)行數(shù)據(jù)的提取,對(duì)于如下的代碼: <div id="test1">大家好!</div> 使用xpath提取是非常方便的。假設(shè)網(wǎng)頁(yè)的源代碼在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] 又只能提取到“你的微信是多少?” 可是我本意是想把“美女,你的微信是多少?”這一整個(gè)句子提取出來(lái)。 <div id="test3">我左青龍,<span id="tiger">右白虎,<ul>上朱雀,<li>下玄武。</li></ul>老牛在當(dāng)中,</span>龍頭在胸口。<div> 而且內(nèi)部的標(biāo)簽還不固定,如果我有一百段這樣類(lèi)似的html代碼,又如何使用xpath表達(dá)式,以最快最方便的方式提取出來(lái)? 使用xpath的string(.) 以第三段代碼為例: data = selector.xpath('//div[@id="test3"]') info = data.xpath('string(.)').extract()[0] 這樣,就可以把“我左青龍,右白虎,上朱雀,下玄武。老牛在當(dāng)中,龍頭在胸口”整個(gè)句子提取出來(lái),賦值給info變量。2.3 非結(jié)構(gòu)化數(shù)據(jù)之 lxml 庫(kù)
lxml 是一種使用 Python 編寫(xiě)的庫(kù),可以迅速、靈活地處理 XML ,支持 XPath (XML Path Language),可以利用 XPath 語(yǔ)法快速的定位特定元素以及節(jié)點(diǎn)信息,提取出 HTML、XML 目標(biāo)數(shù)據(jù)
lxml python 官方文檔?http://lxml.de/index.html
簡(jiǎn)單使用
首先利用 lxml 來(lái)解析 HTML 代碼,先來(lái)一個(gè)小例子來(lái)感受一下它的基本用法。使用 lxml 的 etree 庫(kù),然后利用 etree.HTML 初始化,然后我們將其打印出來(lái)。
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)所以輸出結(jié)果如下,不僅補(bǔ)全了 li 標(biāo)簽,還添加了 body,html 標(biāo)簽。
<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 實(shí)例測(cè)試
(1)獲取所有的 <li> 標(biāo)簽
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]))- 每個(gè)元素都是 Element 類(lèi)型;是一個(gè)個(gè)的標(biāo)簽元素,類(lèi)似現(xiàn)在的實(shí)例
? ? ? ? <Element li at 0x1014e0e18> Element類(lèi)型代表的就是
? ? ? ? <li class="item-0"><a href="link1.html">first item</a></li>
[注意]:Element 類(lèi)型是一種靈活的容器對(duì)象,用于在內(nèi)存中存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)。每個(gè) element 對(duì)象都具有以下屬性:
1. tag:string對(duì)象,標(biāo)簽,用于標(biāo)識(shí)該元素表示哪種數(shù)據(jù)(即元素類(lèi)型)。
2. attrib:dictionary對(duì)象,表示附有的屬性。
3. text:string對(duì)象,表示element的內(nèi)容。
4. tail:string對(duì)象,表示element閉合之后的尾跡。
(2)獲取 <li> 標(biāo)簽的所有 class
html.xpath('//li/@class')# 運(yùn)行結(jié)果:['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0'](3)獲取 <li> 標(biāo)簽下屬性 href 為 link1.html 的 <a> 標(biāo)簽
html.xpath('//li/a[@href="link1.html"]')# 運(yùn)行結(jié)果:[<Element a at 0x10ffaae18>](4)獲取 <li> 標(biāo)簽下的所有 <span> 標(biāo)簽
注意這么寫(xiě)是不對(duì)的 html.xpath('//li/span') 因?yàn)?/ 是用來(lái)獲取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用雙斜杠html.xpath('//li//span') # 運(yùn)行結(jié)果:[<Element span at 0x10d698e18>](5)獲取 <li> 標(biāo)簽下的所有 class,不包括 <li>
html.xpath('//li/a//@class') # 運(yùn)行結(jié)果:['blod'](6)獲取最后一個(gè) <li> 的<a> 的 href
html.xpath('//li[last()]/a/@href') # 運(yùn)行結(jié)果:['link5.html'](7)獲取 class 為 bold 的標(biāo)簽
result = html.xpath('//*[@class="bold"]') print result[0].tag # 運(yùn)行結(jié)果:span騰訊招聘:https://careers.tencent.com/search.html?pcid=40001
2.4 非結(jié)構(gòu)化數(shù)據(jù)之 CSS Selector(CSS 選擇器)
CSS 即層疊樣式表Cascading Stylesheet。?Selector 來(lái)定位(locate)頁(yè)面上的元素(Elements)。?Selenium 官網(wǎng)的Document 里極力推薦使用 CSS locator,而不是 XPath 來(lái)定位元素,原因是 CSS locator 比 XPath locator 速度快。
Beautiful Soup
安裝:https://pypi.org/project/bs4/
官方文檔鏈接:?https://www.crummy.com/software/BeautifulSoup/bs4/doc/
Beautiful Soup 是從 HTML 或 XML 文件中提取數(shù)據(jù),支持 Python 標(biāo)準(zhǔn)庫(kù)中的 HTML 解析器。還支持一些第三方的解析器,其中一個(gè)是 lxml。
Beautiful Soup 自動(dòng)將輸入文檔轉(zhuǎn)換為 Unicode 編碼,輸出文檔轉(zhuǎn)換為 utf-8 編碼。你不需要考慮編碼方式,除非文檔沒(méi)有指定一個(gè)編碼方式,這時(shí),Beautiful Soup 就不能自動(dòng)識(shí)別編碼方式了。然后,你僅僅需要說(shuō)明一下原始編碼方式就可以了。另一個(gè)可供選擇的解析器是純 Python 實(shí)現(xiàn)的 html5lib , html5lib 的解析方式與瀏覽器相同,可以選擇下列方法來(lái)安裝 html5lib: pip install html5lib
下表列出了主要的解析器:
| 解析器 | 使用方法 | 優(yōu)勢(shì) | 劣勢(shì) |
| Python標(biāo)準(zhǔn)庫(kù) | BeautifulSoup(markup, "html.parser") | Python的內(nèi)置標(biāo)準(zhǔn)庫(kù);執(zhí)行速度適中;文檔容錯(cuò)能力強(qiáng) | Python 2.7.3 or 3.2.2前 的版本中文檔容錯(cuò)能力差 |
| lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快;文檔容錯(cuò)能力強(qiáng) ; | 需要安裝C語(yǔ)言庫(kù) |
| lxml XML 解析器 | BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml") | 速度快;唯一支持XML的解析器 | 需要安裝C語(yǔ)言庫(kù) |
| html5lib | BeautifulSoup(markup, "html5lib") | 最好的容錯(cuò)性;以瀏覽器的方式解析文檔;生成HTML5格式的文檔 | 速度慢;不依賴(lài)外部擴(kuò)展 |
推薦使用 lxml 作為解析器,因?yàn)樾矢摺?/strong>在 Python2.7.3 之前的版本和 Python3 中 3.2.2 之前的版本,必須安裝 lxml 或html5lib, 因?yàn)槟切?Python 版本的標(biāo)準(zhǔn)庫(kù)中內(nèi)置的 HTML 解析方法不夠穩(wěn)定.
示例:使用 BeautifulSoup 解析這段代碼,能夠得到一個(gè) BeautifulSoup 的對(duì)象,并能按照標(biāo)準(zhǔn)的縮進(jìn)格式的結(jié)構(gòu)輸出:
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 對(duì)象的內(nèi)容打印 soup 對(duì)象的內(nèi)容
格式化輸出soup 對(duì)象
print(soup.prettify())CSS 選擇器
在寫(xiě) CSS 時(shí):標(biāo)簽名不加任何修飾。類(lèi)名前加點(diǎn)。id名前加 “#”。?
利用類(lèi)似的方法來(lái)篩選元素,用到的方法是 soup.select(),返回類(lèi)型是 list
通過(guò)標(biāo)簽名查找
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 對(duì)象的內(nèi)容 print(soup.select('title')) # [<title>The Dormouse's story</title>] print(soup.select('a')) print(soup.select('b')) # [<b>The Dormouse's story</b>]通過(guò)類(lèi)名查找
print(soup.select('.sister'))通過(guò) id 名查找
print(soup.select('#link1')) #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]直接子標(biāo)簽查找
print(soup.select("head > title")) #[<title>The Dormouse's story</title>]組合查找:組合查找即標(biāo)簽名與類(lèi)名、id名進(jìn)行的組合原理是一樣的,例如查找 p 標(biāo)簽中,id 等于 link1的內(nèi)容,
屬性 和 標(biāo)簽 不屬于 同一節(jié)點(diǎn) ?二者需要用 ?空格分開(kāi)
print(soup.select('p #link1')) #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]屬性查找
查找時(shí)還可以加入屬性元素,屬性需要用中括號(hào)括起來(lái)
注意:屬性和標(biāo)簽屬于同一節(jié)點(diǎn),所以中間不能加空格,否則會(huì)無(wú)法匹配到
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>]同樣,屬性仍然可以與上述查找方式組合。不在同一節(jié)點(diǎn)的使用空格隔開(kāi),在同一節(jié)點(diǎn)的則不用加空格
print soup.select('p a[href="http://example.com/elsie"]') #[<a class="sister" href="http://example.com/elsie" id="link1"></a>]以上的 select 方法返回的結(jié)果都是列表形式,可以遍歷形式輸出
用 get_text() 方法來(lái)獲取它的內(nèi)容。
print soup.select('title')[0].get_text() for title in soup.select('title'):print(title.get_text())Tag:Tag 是什么?通俗點(diǎn)講就是 HTML 中的一個(gè)個(gè)標(biāo)簽,例如:
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>print type(soup.select('a')[0])輸出:bs4.element.Tag對(duì)于 Tag,它有兩個(gè)重要的屬性,是 name 和 attrs,下面我們分別來(lái)感受一下
1. name
print(soup.name) print(soup.select('a')[0].name)輸出: [document] 'a'soup 對(duì)象本身比較特殊,它的 name 即為 [document],對(duì)于其他內(nèi)部標(biāo)簽,輸出的值便為標(biāo)簽本身的名稱(chēng)。
2. attrs
print(soup.select('a')[0].attrs)輸出: {'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'} 在這里,我們把 soup.select('a')[0] 標(biāo)簽的所有屬性打印輸出了出來(lái),得到的類(lèi)型是一個(gè)字典。 如果我們想要單獨(dú)獲取某個(gè)屬性,可以這樣,例如我們獲取它的 class 叫什么print soup.select('a')[0].attrs['class']輸出: ['sister']實(shí)戰(zhàn)案例:騰訊招聘網(wǎng)站:https://careers.tencent.com/search.html
2.5 正則表達(dá)式
掌握了 XPath、CSS選擇器,為什么還要學(xué)習(xí)正則?
正則表達(dá)式,用標(biāo)準(zhǔn)正則解析,一般會(huì)把 HTML當(dāng) 做普通文本,用指定格式匹配當(dāng)相關(guān)文本,適合小片段文本,或者某一串字符(比如電話(huà)號(hào)碼、郵箱賬戶(hù)),或者HTML包含javascript的代碼,無(wú)法用CSS選擇器或者XPath
在線正則表達(dá)式測(cè)試網(wǎng)站:https://tool.oschina.net/regex/
Python 正則表達(dá)式 官方文檔:https://docs.python.org/zh-cn/3.8/library/re.html#regular-expression-objects
正則表達(dá)式常見(jiàn)概念
邊界匹配
^ -- 與字符串開(kāi)始的地方匹配,不匹配任何字符;
$ -- 與字符串結(jié)束的地方匹配,不匹配任何字符;
\b -- 匹配一個(gè)單詞邊界,也就是單詞和空格之間的位置,不匹配任何字符;
"er\b"可以匹配"never"中的"er",但不能匹配"verb"中的"er"。\B -- \b取非,即匹配一個(gè)非單詞邊界;
"er\B"能匹配"verb"中的"er",但不能匹配"never"中的"er"。數(shù)量詞的貪婪模式與非貪婪模式
正則表達(dá)式通常用于在文本中查找匹配的字符串。Python里數(shù)量詞默認(rèn)是貪婪的(在少數(shù)語(yǔ)言里也可能是默認(rèn)非貪婪),總是嘗試匹配盡可能多的字符;非貪婪的則相反,總是嘗試匹配盡可能少的字符。例如:
正則表達(dá)式"ab*"如果用于查找"abbbc",將找到"abbb"。而如果使用非貪婪的數(shù)量詞"ab*?",將找到"a"。反斜杠問(wèn)題
與大多數(shù)編程語(yǔ)言相同,正則表達(dá)式里使用"\"作為轉(zhuǎn)義字符,這就可能造成反斜杠困擾。
假如你需要匹配文本中的字符"\",那么使用編程語(yǔ)言表示的正則表達(dá)式里將需要4個(gè)反斜杠"\\\\":前兩個(gè)和后兩個(gè)分別用于在編程語(yǔ)言里轉(zhuǎn)義成反斜杠,轉(zhuǎn)換成兩個(gè)反斜杠后再在正則表達(dá)式里轉(zhuǎn)義成一個(gè)反斜杠。
Python里的原生字符串很好地解決了這個(gè)問(wèn)題,這個(gè)例子中的正則表達(dá)式可以使用r"\\"表示。
同樣,匹配一個(gè)數(shù)字的"\\d"可以寫(xiě)成r"\d"。有了原生字符串,你再也不用擔(dān)心是不是漏寫(xiě)了反斜杠,寫(xiě)出來(lái)的表達(dá)式也更直觀。
Python Re模塊
Python 自帶了 re 模塊,它提供了對(duì)正則表達(dá)式的支持。
match 函數(shù):re.match 嘗試從字符串的起始位置匹配一個(gè)模式,如果不是起始位置匹配成功的話(huà),match() 就返回 none。
下面是此函數(shù)的語(yǔ)法:re.match(pattern, string, flags=0)
| 參數(shù) | 描述 |
| pattern | 這是正則表達(dá)式來(lái)進(jìn)行匹配。 |
| string | 這是字符串,這將被搜索匹配的模式,在字符串的開(kāi)頭。 |
| flags | 標(biāo)志位,用于控制正則表達(dá)式的匹配方式,如:是否區(qū)分大小寫(xiě),多行匹配等等。 |
匹配成功則 re.match 方法返回一個(gè)匹配的對(duì)象,否則返回 None。
我們可以使用group(num) 或 groups() 匹配對(duì)象函數(shù)來(lái)獲取匹配表達(dá)式。
| 匹配對(duì)象的方法 | 描述 |
| group(num=0) | 此方法返回整個(gè)匹配(或指定分組num) |
| groups() | 此方法返回所有元組匹配的子組(空,如果沒(méi)有) |
示例代碼:
#!/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 '''正則表達(dá)式修飾符 - 選項(xiàng)標(biāo)志
正則表達(dá)式字面可以包含一個(gè)可選的修飾符來(lái)控制匹配的各個(gè)方面。修飾符被指定為一個(gè)可選的標(biāo)志??梢允褂卯惢蛱峁┒鄠€(gè)修飾符(|),如先前所示,并且可以由這些中的一個(gè)來(lái)表示:
| 修飾符 | 描述 |
| re.I(re.IGNORECASE) | 使匹配對(duì)大小寫(xiě)不敏感 |
| re.M(MULTILINE) | 多行匹配,影響 ^ 和 $ |
| re.S(DOTALL) | 使 . 匹配包括換行在內(nèi)的所有字符 |
| re.X(VERBOSE) | 正則表達(dá)式可以是多行,忽略空白字符,并可以加入注釋 |
findall() 函數(shù)
re.findall(pattern, string, flags=0)
返回字符串中所有模式的非重疊的匹配,作為字符串列表。該字符串掃描左到右,并匹配返回的順序發(fā)現(xiàn)
默認(rèn):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} #區(qū)號(hào)-\d{8}''' #號(hào)碼re.findall(reStr,"010-12345678",re.X) ["010-12345678"]search 函數(shù)
re.search 掃描整個(gè)字符串并返回第一個(gè)成功的匹配。
下面是此函數(shù)語(yǔ)法:re.search(pattern, string, flags=0)
| 參數(shù) | 描述 |
| pattern | 這是正則表達(dá)式來(lái)進(jìn)行匹配。 |
| string | 這是字符串,這將被搜索到的字符串中的任何位置匹配的模式。 |
| flags | 標(biāo)志位,用于控制正則表達(dá)式的匹配方式,如:是否區(qū)分大小寫(xiě),多行匹配等等。 |
匹配成功則 re.search 方法返回一個(gè)匹配的對(duì)象,否則返回None。
我們可以使用group(num) 或 groups() 匹配對(duì)象函數(shù)來(lái)獲取匹配表達(dá)式。
#!/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 的區(qū)別
- re.match? 只匹配字符串的開(kāi)始,如果字符串開(kāi)始不符合正則表達(dá)式,則匹配失敗,函數(shù)返回None;
- re.search 匹配整個(gè)字符串,直到找到一個(gè)匹配。
示例代碼:
#!/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 用于替換字符串中的匹配項(xiàng)。
語(yǔ)法:re.sub(pattern, repl, string, max=0)
返回的字符串是在字符串中用 RE 最左邊不重復(fù)的匹配來(lái)替換。如果模式?jīng)]有發(fā)現(xiàn),字符將被沒(méi)有改變地返回。 可選參數(shù) count 是模式匹配后替換的最大次數(shù);count 必須是非負(fù)整數(shù)。缺省值是 0 表示替換所有的匹配。
實(shí)例:
#!/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}")# 當(dāng)執(zhí)行上面的代碼,產(chǎn)生以下結(jié)果: # Next Url : http://hr.tencent.com/position.php?&start=20正則表達(dá)式語(yǔ)法
2.6 頁(yè)面解析之結(jié)構(gòu)化數(shù)據(jù)
結(jié)構(gòu)化的數(shù)據(jù)是最好處理,一般都是類(lèi)似 JSON 格式的字符串,直接解析 JSON 數(shù)據(jù),提取 JSON 的關(guān)鍵字段即可。
JSON (JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換格式;適用于進(jìn)行數(shù)據(jù)交互的場(chǎng)景,比如網(wǎng)站前臺(tái)與后臺(tái)之間的數(shù)據(jù)交互。Python 自帶了 JSON 模塊,直接 import json 就可以使用了。Json 模塊提供了四個(gè)功能:dumps、dump、loads、load,用于字符串 和 python數(shù)據(jù)類(lèi)型間進(jìn)行轉(zhuǎn)換
Python 操作 json 的標(biāo)準(zhǔn) api 庫(kù)參考:https://docs.python.org/zh-cn/3/library/json.html
在線 JSON 格式化代碼:https://tool.oschina.net/codeformat/json
1. json.loads()
作用:json字符串 轉(zhuǎn)化 python 的類(lèi)型,返回一個(gè)python的類(lèi)型
從 json 到 python 的類(lèi)型轉(zhuǎn)化對(duì)照如下:
示例 :
import jsona = "[1,2,3,4]" b = '{"k1":1,"k2":2}' # 當(dāng)字符串為字典時(shí){}外面必須是''單引號(hào){}里面必須是""雙引號(hào) print(json.loads(a)) # [1, 2, 3, 4]print(json.loads(b)) # {'k2': 2, 'k1': 1}豆瓣讀書(shū) 示例:
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()
實(shí)現(xiàn) python 類(lèi)型轉(zhuǎn)化為 json 字符串,返回一個(gè) str 對(duì)象
從 python 原始類(lèi)型向 json 類(lèi)型的轉(zhuǎn)化對(duì)照如下:
示例:
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 參數(shù)引起的中文編碼問(wèn)題
如果 Python Dict 字典含有中文,json.dumps 序列化時(shí)對(duì)中文默認(rèn)使用的 ascii 編碼
import chardet import jsonb = {"name":"中國(guó)"}json.dumps(b) # '{"name": "\\u4e2d\\u56fd"}'print json.dumps(b) # {"name": "\u4e2d\u56fd"}chardet.detect(json.dumps(b)) # {'confidence': 1.0, 'encoding': 'ascii'}'中國(guó)' 中的 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 類(lèi)型 以 字符串的形式 寫(xiě)到文件中
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 形式的字符串元素 轉(zhuǎn)化成 python 類(lèi)型
# -*- 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']實(shí)戰(zhàn)項(xiàng)目:獲取 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 信息抽取類(lèi)庫(kù),從JSON文檔中抽取指定信息的工具
- JSONPath 與 Xpath 區(qū)別:JsonPath 對(duì)于 JSON 來(lái)說(shuō),相當(dāng)于 XPATH 對(duì)于XML。
下載地址:https://pypi.python.org/pypi/jsonpath/
安裝方法:下載 jsonpath,解壓之后執(zhí)行 '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 感覺(jué)跟操作 JSON 一樣
Python 操作 XML 的第三方庫(kù)參考:https://github.com/martinblech/xmltodict
模塊安裝:pip install xmltodict
數(shù)據(jù)提取總結(jié)
- HTML、XML:XPath、CSS選擇器、正則表達(dá)式、轉(zhuǎn)化成 Python 類(lèi)型(xmltodict)
- JSON:JSONPath,轉(zhuǎn)化成 Python 類(lèi)型進(jìn)行操作(json類(lèi))
- 其他(js、文本、電話(huà)號(hào)碼、郵箱地址):正則表達(dá)式
3. 爬蟲(chóng)實(shí)踐篇
右鍵 ---> 查看源代碼 和 F12? 區(qū)別:
- 右鍵查看源代碼:實(shí)質(zhì)是一個(gè) Get 請(qǐng)求
- F12 是整個(gè)頁(yè)面 所有的請(qǐng)求 url 加載完成的頁(yè)面
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
解決問(wèn)題思路:
- 1. 確認(rèn)需求數(shù)據(jù)在哪。右鍵查看源代碼
- 2. Fidder 模擬發(fā)送數(shù)據(jù)
代碼示例:https://cuiqingcai.com/993.html
3.2 ?案例 2:(?惠州市網(wǎng)上掛牌交易系統(tǒng)? )
為例: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 基本用法 與 藥品監(jiān)督管理局 )
Requests
優(yōu)點(diǎn):Requests 繼承了 urllib2 的所有特性。Requests 支持 HTTP 連接保持和連接池,支持使用 cookie 保持會(huì)話(huà),支持文件上傳,支持自動(dòng)確定響應(yīng)內(nèi)容的編碼,支持國(guó)際化的 URL 和 POST 數(shù)據(jù)自動(dòng)編碼。
缺陷:requests 不是 python 自帶的庫(kù),需要另外安裝 easy_install or pip install。直接使用不能異步調(diào)用,速度慢(自動(dòng)確定響應(yīng)內(nèi)容的編碼)。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'})以 藥品監(jiān)督管理局 為例,采集 藥品 --->?國(guó)產(chǎn)藥品 下的所有的商品信息:http://app1.nmpa.gov.cn/data_nmpa/face3/base.jsp?tableId=25&tableName=TABLE25&title=國(guó)產(chǎn)藥品&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("國(guó)產(chǎn)藥品商品名"),'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')查看運(yùn)行結(jié)果,感受一下。?
總結(jié)
1. User-Agent 偽裝 Chrome,欺騙 web 服務(wù)器
2. urlencode 字典類(lèi)型 Dict、元祖 轉(zhuǎn)化成 url query 字符串
1. 完成商品詳情頁(yè)采集
2. 完成整個(gè)項(xiàng)目的采集
詳情頁(yè)
# -*- 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)完整項(xiàng)目
# -*- 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("國(guó)產(chǎn)藥品商品名"),'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("國(guó)產(chǎn)藥品商品名") + "&", url)ParseDetail('http://app1.sfda.gov.cn/datasearch/face3/' + url.encode('gb2312'))if __name__ == '__main__':main()3.4 案例 4:(?拉鉤招聘網(wǎng) )
以拉鉤具體詳情頁(yè)為例,進(jìn)行抓取。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并抓取頁(yè)面代碼,首先我們確定好頁(yè)面的URL是 http://www.qiushibaike.com/8hr/page/4, 其中最后一個(gè)數(shù)字1代表頁(yè)數(shù),我們可以傳入不同的值來(lái)獲得某一頁(yè)的段子內(nèi)容。我們初步構(gòu)建如下的代碼來(lái)打印頁(yè)面代碼內(nèi)容試試看,先構(gòu)造最基本的頁(yè)面抓取方式,看看會(huì)不會(huì)成功。在Composer raw 模擬發(fā)送數(shù)據(jù)
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 報(bào)錯(cuò)。應(yīng)該是 headers 驗(yàn)證的問(wèn)題,加上一個(gè) headers 驗(yàn)證試試看
# -*- 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多線程爬蟲(chóng) 實(shí)戰(zhàn):糗事百科
python 下多線程的思考
Queue 是 python 中的標(biāo)準(zhǔn)庫(kù),可以直接 import Queue 引用; 隊(duì)列是線程間最常用的交換數(shù)據(jù)的形式。對(duì)于共享資源,加鎖是個(gè)重要的環(huán)節(jié)。因?yàn)?python 原生的 list,dict 等,都是 not thread safe 的。而 Queue,是線程安全的,因此在滿(mǎn)足使用條件下,建議使用隊(duì)列。
Python Queue 模塊有三種隊(duì)列及構(gòu)造函數(shù):
1、Python Queue模塊的FIFO隊(duì)列先進(jìn)先出。 class Queue.Queue(maxsize) 2、LIFO類(lèi)似于堆,即先進(jìn)后出。 class Queue.LifoQueue(maxsize) 3、還有一種是優(yōu)先級(jí)隊(duì)列級(jí)別越低越先出來(lái)。 class Queue.PriorityQueue(maxsize)Queue(隊(duì)列對(duì)象)
初始化: class Queue.Queue(maxsize) FIFO 先進(jìn)先出
包中的常用方法:
Queue.qsize() 返回隊(duì)列的大小 Queue.empty() 如果隊(duì)列為空,返回True,反之False Queue.full() 如果隊(duì)列滿(mǎn)了,返回True,反之False Queue.full 與 maxsize 大小對(duì)應(yīng) Queue.get([block[, timeout]])獲取隊(duì)列,timeout等待時(shí)間調(diào)用隊(duì)列對(duì)象的 get()方法從隊(duì)頭刪除并返回一個(gè)項(xiàng)目??蛇x參數(shù)為block,默認(rèn)為T(mén)rue。
如果隊(duì)列為空且 block 為 True,get() 就使調(diào)用線程暫停,直至有項(xiàng)目可用。
如果隊(duì)列為空且 block 為 False,隊(duì)列將引發(fā) Empty 異常。
示例代碼:
# -*- coding:utf-8 -*- import requests from lxml import etree from Queue import Queue import threading import time import jsonclass thread_crawl(threading.Thread):'''抓取線程類(lèi)'''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'}# 多次嘗試失敗結(jié)束、防止死循環(huán)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):'''頁(yè)面解析類(lèi);'''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:'''調(diào)用隊(duì)列對(duì)象的get()方法從隊(duì)頭刪除并返回一個(gè)項(xiàng)目??蛇x參數(shù)為block,默認(rèn)為T(mén)rue。如果隊(duì)列為空且block為T(mén)rue,get()就使調(diào)用線程暫停,直至有項(xiàng)目可用。如果隊(duì)列為空且block為False,隊(duì)列將引發(fā)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):'''解析網(wǎng)頁(yè)函數(shù):param item: 網(wǎng)頁(yè)內(nèi)容: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')#初始化網(wǎng)頁(yè)頁(yè)碼page從1-10個(gè)頁(yè)面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"]#分別啟動(dòng)parserListfor threadID in parserList:thread = Thread_Parser(threadID, data_queue, lock, output)thread.start()parserthreads.append(thread)# 等待隊(duì)列清空while not pageQueue.empty():pass# 等待所有線程完成for t in crawlthreads:t.join()while not data_queue.empty():pass# 通知線程是時(shí)候退出global exitFlag_ParserexitFlag_Parser = Truefor t in parserthreads:t.join()print "Exiting Main Thread"with lock:output.close()if __name__ == '__main__':main()3.5 模擬登陸 及 驗(yàn)證碼
使用表單登陸
這種情況屬于 post 請(qǐng)求,即先向服務(wù)器發(fā)送表單數(shù)據(jù),服務(wù)器再將返回的 cookie 存入本地。
data = {'data1':'XXXXX', 'data2':'XXXXX'}Requests:data 為 dict,json
import requestsresponse = requests.post(url=url, data=data)使用 cookie 登陸
使用 cookie 登陸,服務(wù)器會(huì)認(rèn)為你是一個(gè)已登陸的用戶(hù),所以就會(huì)返回給你一個(gè)已登陸的內(nèi)容。因此,需要驗(yàn)證碼的情況可以使用帶驗(yàn)證碼登陸的 cookie 解決。
import requests requests_session = requests.session() response = requests_session.post(url=url_login, data=data)若存在驗(yàn)證碼,此時(shí)采用 response = requests_session.post(url=url_login, data=data)是不行的,做法應(yīng)該如下:
response_captcha = requests_session.get(url=url_login, cookies=cookies) response1 = requests.get(url_login) # 未登陸 response2 = requests_session.get(url_login) # 已登陸,因?yàn)橹澳玫搅薘esponse Cookie! response3 = requests_session.get(url_results) # 已登陸,因?yàn)橹澳玫搅薘esponse Cookie!實(shí)戰(zhàn)項(xiàng)目:登錄豆瓣,并發(fā)表動(dòng)態(tài)
模擬登陸的重點(diǎn),在于找到表單真實(shí)的提交地址,然后攜帶cookie,post數(shù)據(jù)即可,只要登陸成功,我們就可以訪問(wèn)其他任意網(wǎng)頁(yè),從而獲取網(wǎng)頁(yè)內(nèi)容。
一個(gè)請(qǐng)求,只要正確模擬了method,url,header,body 這四要素,任何內(nèi)容都能抓下來(lái),而所有的四個(gè)要素,只要打開(kāi)瀏覽器-審查元素-Network就能看到!
驗(yàn)證碼這一塊,現(xiàn)在主要是先把驗(yàn)證碼的圖片保存下來(lái),手動(dòng)輸入驗(yàn)證碼,后期可以研究下 python 自動(dòng)識(shí)別驗(yàn)證碼。
但是驗(yàn)證碼保存成本地圖片,看的不不太清楚(有時(shí)間在改下),可以把驗(yàn)證碼的 url 地址在瀏覽器中打開(kāi),就可以看清楚驗(yàn)證碼了。
主要實(shí)現(xiàn) 登錄豆瓣,并發(fā)表一句話(huà)
# -*- coding:utf-8 -*-import re import requests from bs4 import BeautifulSoupclass DouBan(object):def __init__(self):self.__username = "豆瓣帳號(hào)" # 豆瓣帳號(hào)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) # 驗(yàn)證碼存在if captcha_address:# 利用正則表達(dá)式獲取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("登錄不需要驗(yàn)證碼") # 不需要驗(yàn)證碼的邏輯 和 上面輸入驗(yàn)證碼之后 的 邏輯 一樣# 此處代碼省略else:print("login fail", r.status_code) passif __name__ == "__main__":t = DouBan()t.login()pass運(yùn)行截圖:
登錄豆瓣帳號(hào),可以看到說(shuō)了一句話(huà) “模擬登錄”
Python 性能優(yōu)化
因?yàn)?GIL 的存在,Python很難充分利用多核 CPU 的優(yōu)勢(shì)。
但是,可以通過(guò)內(nèi)置的模塊 multiprocessing 實(shí)現(xiàn)下面幾種并行模式:
- 1、 多進(jìn)程并行編程:對(duì)于CPU密集型的程序,可以使用multiprocessing的Process、Pool 等封裝好的類(lèi),通過(guò)多進(jìn)程的方式實(shí)現(xiàn)并行計(jì)算。但是因?yàn)檫M(jìn)程中的通信成本比較大,對(duì)于進(jìn)程之間需要大量數(shù)據(jù)交互的程序效率未必有大的提高。
- 2、 多線程并行編程:對(duì)于IO密集型的程序,multiprocessing.dummy 模塊使用 multiprocessing 的接口封裝 threading,使得多線程編程也變得非常輕松(比如可以使用Pool的map接口,簡(jiǎn)潔高效)。
- 3、分布式:multiprocessing 中的 Managers 類(lèi) 提供了可以在不同進(jìn)程之共享數(shù)據(jù)的方式,可以在此基礎(chǔ)上開(kāi)發(fā)出分布式的程序。 不同的業(yè)務(wù)場(chǎng)景可以選擇其中的一種或幾種的組合實(shí)現(xiàn)程序性能的優(yōu)化。
總結(jié)
以上是生活随笔為你收集整理的爬虫教程( 1 ) --- 初级、基础、实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ftw遍历目录树
- 下一篇: vim中正则表达式匹配单词边界