序言
第1章 Scrapy介紹
第2章 理解HTML和XPath
第3章 爬蟲基礎(chǔ)
第4章 從Scrapy到移動(dòng)應(yīng)用
第5章 快速構(gòu)建爬蟲
第6章 Scrapinghub部署
第7章 配置和管理
第8章 Scrapy編程
第9章 使用Pipeline
第10章 理解Scrapy的性能
第11章(完) Scrapyd分布式抓取和實(shí)時(shí)分析
為了從網(wǎng)頁(yè)提取信息揉抵,了解網(wǎng)頁(yè)的結(jié)構(gòu)是非常必要的。我們會(huì)快速學(xué)習(xí)HTML嗤疯、HTML的樹結(jié)構(gòu)和用來(lái)篩選網(wǎng)頁(yè)信息的XPath冤今。
HTML、DOM樹結(jié)構(gòu)和XPath
從這本書的角度茂缚,鍵入網(wǎng)址到看見網(wǎng)頁(yè)的整個(gè)過程可以分成四步:
- 在瀏覽器中輸入網(wǎng)址URL戏罢。URL的第一部分,也即域名(例如gumtree.com)屋谭,用來(lái)搜尋網(wǎng)絡(luò)上的服務(wù)器。URL和其他像cookies等數(shù)據(jù)形成了一個(gè)發(fā)送到服務(wù)器的請(qǐng)求request龟糕。
- 服務(wù)器向?yàn)g覽器發(fā)送HTML桐磁。服務(wù)器也可能發(fā)送XML或JSON等其他格式,目前我們只關(guān)注HTML讲岁。
- HTML在瀏覽器內(nèi)部轉(zhuǎn)化成樹結(jié)構(gòu):文檔對(duì)象模型(DOM)我擂。
- 根據(jù)布局規(guī)范,樹結(jié)構(gòu)轉(zhuǎn)化成屏幕上的真實(shí)頁(yè)面催首。
研究下這四個(gè)步驟和樹結(jié)構(gòu)扶踊,可以幫助定位要抓取的文本和編寫爬蟲。
URL
URL包括兩部分:第一部分通過DNS定位服務(wù)器郎任,例如當(dāng)你在瀏覽器輸入https://mail.google.com/mail/u/0/#inbox這個(gè)地址時(shí),產(chǎn)生了一個(gè)mail.google.com的DNS請(qǐng)求备籽,后者為你解析了一臺(tái)服務(wù)器的IP地址舶治,例如173.194.71.83。也就是說车猬,https://mail.google.com/mail/u/0/#inbox轉(zhuǎn)換成了https://173.194.71.83/mail/u/0/#inbox霉猛。
URL其余的部分告訴服務(wù)器這個(gè)請(qǐng)求具體是關(guān)于什么的,可能是一張圖片珠闰、一份文檔或是觸發(fā)一個(gè)動(dòng)作惜浅,例如在服務(wù)器上發(fā)送一封郵件。
HTML文檔
服務(wù)器讀取URL伏嗜,了解用戶請(qǐng)求坛悉,然后回復(fù)一個(gè)HTML文檔。HTML本質(zhì)是一個(gè)文本文件承绸,可以用TextMate裸影、Notepad、vi或Emacs等軟件打開军熏。與大多數(shù)文本文件不同轩猩,HTML嚴(yán)格遵循萬(wàn)維網(wǎng)聯(lián)盟(World Wide Web Consortium)的規(guī)定格式。這個(gè)格式超出了本書的范疇荡澎,這里只看一個(gè)簡(jiǎn)單的HTML頁(yè)面均践。如果你打開http://example.com,點(diǎn)擊查看源代碼摩幔,就可以看到HTML代碼彤委,如下所示:
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type"
content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,
initial-scale=1" />
<style type="text/css"> body { background-color: ...
} </style>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is established to be used for
illustrative examples examples in documents.
You may use this domain in examples without
prior coordination or asking for permission.</p>
<p><a >
More information...</a></p>
</div>
</body>
</html>
為了便于閱讀,我美化了這個(gè)HTML文檔热鞍。你也可以把整篇文檔放在一行里葫慎。對(duì)于HTML衔彻,大多數(shù)情況下,空格和換行符不會(huì)造成什么影響偷办。
尖括號(hào)里的字符稱作標(biāo)簽艰额,例如<html>或<head>。<html>是起始標(biāo)簽椒涯,</html>是結(jié)束標(biāo)簽柄沮。標(biāo)簽總是成對(duì)出現(xiàn)。某些網(wǎng)頁(yè)沒有結(jié)束標(biāo)簽废岂,例如只用<p>標(biāo)簽分隔段落祖搓,瀏覽器對(duì)這種行為是容許的,會(huì)智能判斷哪里該有結(jié)束標(biāo)簽</p>湖苞。
<p>與</p>之間的內(nèi)容稱作HTML的元素拯欧。元素之間可以嵌套元素,比如例子中的<div>標(biāo)簽财骨,和第二個(gè)<p>標(biāo)簽镐作,后者包含了一個(gè)<a>標(biāo)簽。
有些標(biāo)簽稍顯復(fù)雜隆箩,例如<a >该贾,帶有URL的href部分稱作屬性。
最后捌臊,許多標(biāo)簽元素包含有文本杨蛋,例如<h1>標(biāo)簽中的Example Domain。對(duì)我們而言理澎,<body>標(biāo)簽之間的可見內(nèi)容更為重要逞力。頭部標(biāo)簽<head>中指明了編碼字符,由Scrapy對(duì)其處理矾端,就不用我們浪費(fèi)精力了掏击。
樹結(jié)構(gòu)
不同的瀏覽器有不同的借以呈現(xiàn)網(wǎng)頁(yè)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。但DOM樹是跨平臺(tái)且不依賴語(yǔ)言的秩铆,可以被幾乎所有瀏覽器支持砚亭。
只需右鍵點(diǎn)擊,選擇查看元素殴玛,就可以在瀏覽器中查看網(wǎng)頁(yè)的樹結(jié)構(gòu)捅膘。如果這項(xiàng)功能被禁止了,可以在選項(xiàng)的開發(fā)者工具中修改滚粟。
你看到的樹結(jié)構(gòu)和HTML很像寻仗,但不完全相同。無(wú)論原始HTML文件使用了多少空格和換行符凡壤,樹結(jié)構(gòu)看起來(lái)都會(huì)是一樣的署尤。你可以點(diǎn)擊任意元素耙替,或是改變屬性,這樣可以實(shí)時(shí)看到對(duì)HTML網(wǎng)頁(yè)產(chǎn)生了什么變化曹体。例如俗扇,如果你雙擊了一段文字,并修改了它箕别,然后點(diǎn)擊回車铜幽,屏幕上這段文字就會(huì)根據(jù)新的設(shè)置發(fā)生改變。在右邊的方框中串稀,在屬性標(biāo)簽下面除抛,你可以看到這個(gè)樹結(jié)構(gòu)的屬性列表。在頁(yè)面底部母截,你可以看到一個(gè)面包屑路徑到忽,指示著選中元素的所在位置。
重要的是記住微酬,HTML是文本绘趋,而樹結(jié)構(gòu)是瀏覽器內(nèi)存中的一個(gè)對(duì)象,你可以通過程序查看颗管、操作這個(gè)對(duì)象。在Chrome瀏覽器中滓走,就是通過開發(fā)者工具查看垦江。
瀏覽器中的頁(yè)面
HTML文本和樹結(jié)構(gòu)和我們平時(shí)在瀏覽器中看到的頁(yè)面截然不同。這恰恰是HTML的成功之處搅方。HTML文件就是要具有可讀性比吭,可以區(qū)分網(wǎng)頁(yè)的內(nèi)容,但不是按照呈現(xiàn)在屏幕上的方式姨涡。這意味著衩藤,呈現(xiàn)HTML文檔、進(jìn)行美化都是瀏覽器的職責(zé)涛漂,無(wú)論是對(duì)于功能齊備的Chrome赏表、移動(dòng)端瀏覽器、還是Lynx這樣的文本瀏覽器匈仗。
也就是說瓢剿,網(wǎng)頁(yè)的發(fā)展對(duì)網(wǎng)頁(yè)開發(fā)者和用戶都提出了極大的開發(fā)網(wǎng)頁(yè)方面的需求。CSS就是這樣被發(fā)明出來(lái)悠轩,用以服務(wù)HTML元素间狂。對(duì)于Scrapy,我們不涉及CSS火架。
既然如此鉴象,樹結(jié)構(gòu)對(duì)呈現(xiàn)出來(lái)的網(wǎng)頁(yè)有什么作用呢忙菠?答案就是盒模型。正如DOM樹可以包含其它元素或是文字纺弊,同樣的牛欢,盒模型里面也可以內(nèi)嵌其它內(nèi)容。所以俭尖,我們?cè)谄聊簧峡吹降木W(wǎng)頁(yè)是原始HTML的二維呈現(xiàn)氢惋。樹結(jié)構(gòu)是其中的一維,但它是隱藏的稽犁。例如焰望,在下圖中,我們看到三個(gè)DOM元素已亥,一個(gè)<div>和兩個(gè)內(nèi)嵌的<h1>和<p>熊赖,出現(xiàn)在瀏覽器和DOM中:
用XPath選擇HTML元素
如果你以前接觸過傳統(tǒng)的軟件工程,并不知道XPath虑椎,你可能會(huì)擔(dān)心震鹉,在HTML文檔中查詢某個(gè)信息,要進(jìn)行復(fù)雜的字符串匹配捆姜、搜索標(biāo)簽传趾、處理特殊字符、解析整個(gè)樹結(jié)構(gòu)等繁瑣工作泥技。對(duì)于XPath浆兰,所有的這些都不是問題,你可以輕松提取元素珊豹、屬性或是文字簸呈。
在Chrome中使用XPath,在開發(fā)者工具中點(diǎn)擊控制臺(tái)標(biāo)簽店茶,使用$x功能蜕便。例如,在網(wǎng)頁(yè)http://example.com/的控制臺(tái)贩幻,輸入$x('//h1')轿腺,就可以移動(dòng)到<h1>元素,如截圖所示:
你在控制臺(tái)中看到的是一個(gè)包含所選元素的JavaScript數(shù)組段直。如果你將光標(biāo)移動(dòng)到這個(gè)數(shù)組上吃溅,你可以看到被選擇的元素被高亮顯示。這個(gè)功能很有用鸯檬。
XPath表達(dá)式
HTML文檔的層級(jí)結(jié)構(gòu)的最高級(jí)是<html>標(biāo)簽决侈,你可以使用元素名和斜杠線選擇任意元素。例如,下面的表達(dá)式返回了http://example.com/上對(duì)應(yīng)的內(nèi)容:
$x('/html')
[ <html>...</html> ]
$x('/html/body')
[ <body>...</body> ]
$x('/html/body/div')
[ <div>...</div> ]
$x('/html/body/div/h1')
[ <h1>Example Domain</h1> ]
$x('/html/body/div/p')
[ <p>...</p>, <p>...</p> ]
$x('/html/body/div/p[1]')
[ <p>...</p> ]
$x('/html/body/div/p[2]')
[ <p>...</p> ]
注意赖歌,<p>標(biāo)簽在<div>標(biāo)簽內(nèi)有兩個(gè)枉圃,所以會(huì)返回兩個(gè)。你可以用p[1]和p[2]分別返回兩個(gè)元素庐冯。
從抓取的角度孽亲,文檔的標(biāo)題或許是唯一讓人感興趣的,它位于文檔的頭部展父,可以用下面的額表達(dá)式找到:
$x('//html/head/title')
[ <title>Example Domain</title> ]
對(duì)于大文檔返劲,你可能要寫很長(zhǎng)的XPath表達(dá)式,以獲取所要的內(nèi)容栖茉。為了避免這點(diǎn)篮绿,兩個(gè)斜杠線//可以讓你訪問到所有的同名元素。例如吕漂,//p可以選擇所有的p元素亲配,//a可以選擇所有的鏈接。
$x('//p')
[ <p>...</p>, <p>...</p> ]
$x('//a')
[ <a >More information...</a> ]
//a可以用在更多的地方惶凝。例如吼虎,如果要找到所有<div>標(biāo)簽的鏈接,你可以使用//div//a苍鲜。如果a前面只有一個(gè)斜杠思灰,//div/a會(huì)返回空,因?yàn)樵谏厦娴睦又?lt;div>標(biāo)簽下面沒有<a>混滔。
$x('//div//a')
[ <a >More information...</a> ]
$x('//div/a')
[ ]
你也可以選擇屬性官辈。http://example.com/上唯一的屬性是鏈接href,可以通過下面的方式找到:
$x('//a/@href')
[]
你也可以只通過text( )函數(shù)選擇文字:
$x('//a/text()')
["More information..."]
可以使用*標(biāo)志選擇某層下所有的元素遍坟,例如:
$x('//div/*')
[<h1>Example Domain</h1>, <p>...</p>, <p>...</p>]
尋找特定屬性,例如@class晴股、或?qū)傩杂刑囟ㄖ禃r(shí)愿伴,你會(huì)發(fā)現(xiàn)XPath非常好用。例如电湘,//a[@href]可以找到所有鏈接隔节,//a[@href="http://www.iana.org/domains/example"]則進(jìn)行了指定的選擇。
當(dāng)屬性值中包含特定字符串時(shí)寂呛,XPath會(huì)極為方便怎诫。例如,
$x('//a[@href]')
[<a >More information...</a>]
$x('//a[@)
[<a >More information...</a>]
$x('//a[contains(@href, "iana")]')
[<a >More information...</a>]
$x('//a[starts-with(@href, "http://www.")]')
[<a >More information...</a>]
$x('//a[not(contains(@href, "abc"))]')
[ <a >More information...</a>]
在http://www.w3schools.com/xsl/xsl_functions.asp在線文檔中你可以找到更多類似的函數(shù)贷痪,但并非都常用幻妓。
在Scrapy終端中可以使用同樣的命令,在命令行中輸入
scrapy shell "http://example.com"
終端會(huì)向你展示許多寫爬蟲時(shí)碰到的變量劫拢。其中最重要的是響應(yīng)肉津,在HTML中是HtmlResponse强胰,這個(gè)類可以讓你在Chrome使用xpath( )方法$x。下面是一些例子:
response.xpath('/html').extract()
[u'<html><head><title>...</body></html>']
response.xpath('/html/body/div/h1').extract()
[u'<h1>Example Domain</h1>']
response.xpath('/html/body/div/p').extract()
[u'<p>This domain ... permission.</p>', u'<p><a >More information...</a></p>']
response.xpath('//html/head/title').extract()
[u'<title>Example Domain</title>']
response.xpath('//a').extract()
[u'<a >More information...</a>']
response.xpath('//a/@href').extract()
[u'http://www.iana.org/domains/example']
response.xpath('//a/text()').extract()
[u'More information...']
response.xpath('//a[starts-with(@href, "http://www.")]').extract()
[u'<a >More information...</a>']
這意味著妹沙,你可用Chrome瀏覽器生成XPath表達(dá)式偶洋,以便在Scrapy爬蟲中使用。
使用Chrome瀏覽器獲得XPath表達(dá)式
Chrome瀏覽器可以幫助我們獲取XPath表達(dá)式這點(diǎn)確實(shí)對(duì)開發(fā)者非常友好距糖。像之前演示的那樣檢查一個(gè)元素:右鍵選擇一個(gè)元素玄窝,選擇檢查元素。開發(fā)者工具被打開悍引,該元素在HTML的樹結(jié)構(gòu)中被高亮顯示恩脂,可以在右鍵打開的菜單中選擇Copy XPath,表達(dá)式就復(fù)制到粘貼板中了吗铐。
你可以在控制臺(tái)中檢測(cè)表達(dá)式:
$x('/html/body/div/p[2]/a')
[<a >More information...</a>]
常見工作
下面展示一些XPath表達(dá)式的常見使用东亦。先來(lái)看看在維基百科上是怎么使用的。維基百科的頁(yè)面非常穩(wěn)定唬渗,不會(huì)在短時(shí)間內(nèi)改變排版典阵。
- 取得id為firstHeading的div下的span的text:
//h1[@id="firstHeading"]/span/text()
- 取得id為toc的div下的ul內(nèi)的URL:
//div[@id="toc"]/ul//a/@href
- 在任意class包含ltr和class包含skin-vector的元素之內(nèi),取得h1的text镊逝,這兩個(gè)字符串可能在同一class內(nèi)壮啊,或不在。
//*[contains(@class,"ltr") and contains(@class,"skin-vector")]//h1//text()
實(shí)際應(yīng)用中撑蒜,你會(huì)在XPath中頻繁地使用class歹啼。在這幾個(gè)例子中,你需要記住座菠,因?yàn)镃SS的板式原因狸眼,你會(huì)看到HTML的元素總會(huì)包含許多特定的class屬性。這意味著浴滴,有的<div>的class是link拓萌,其他導(dǎo)航欄的<div>的class就是link active。后者是當(dāng)前生效的鏈接升略,因此是可見或是用CSS特殊色高亮顯示的微王。當(dāng)抓取的時(shí)候,你通常是對(duì)含有某個(gè)屬性的元素感興趣的品嚣,就像之前的link和link active炕倘。XPath的contains( )函數(shù)就可以幫你選擇包含某一class的所有元素。
- 選擇class屬性是infobox的table的第一張圖片的URL:
//table[@class="infobox"]//img[1]/@src
- 選擇class屬性是reflist開頭的div下面的所有URL鏈接:
//div[starts-with(@class,"reflist")]//a/@href
- 選擇div下面的所有URL鏈接翰撑,并且這個(gè)div的下一個(gè)相鄰元素的子元素包含文字References:
//*[text()="References"]/../following-sibling::div//a
- 取得所有圖片的URL:
//img/@src
提前應(yīng)對(duì)網(wǎng)頁(yè)發(fā)生改變
爬取的目標(biāo)常常位于遠(yuǎn)程服務(wù)器罩旋。這意味著,如果它的HTML發(fā)生了改變,XPath表達(dá)式就無(wú)效了瘸恼,我們就不得不回過頭修改爬蟲的程序劣挫。因?yàn)榫W(wǎng)頁(yè)的改變一般就很少,爬蟲的改動(dòng)往往不會(huì)很大东帅。然而压固,我們還是寧肯不要回頭修改。一些基本原則可以幫助我們降低表達(dá)式失效的概率:
- 避免使用數(shù)組序號(hào)
Chrome常常會(huì)在表達(dá)式中加入許多常數(shù)
//*[@id="myid"]/div/div/div[1]/div[2]/div/div[1]/div[1]/a/img
如果HTML上有一個(gè)廣告窗的話靠闭,就會(huì)改變文檔的結(jié)構(gòu)帐我,這個(gè)表達(dá)式就會(huì)失效。解決的方法是愧膀,盡量找到離img標(biāo)簽近的元素拦键,根據(jù)該元素的id或class屬性,進(jìn)行抓取檩淋,例如:
//div[@class="thumbnail"]/a/img
- 用class抓取效果不一定好
使用class屬性可以方便的定位要抓取的元素芬为,但是因?yàn)镃SS也要通過class修改頁(yè)面的外觀,所以class屬性可能會(huì)發(fā)生改變蟀悦,例如下面用到的class:
//div[@class="thumbnail"]/a/img
過一段時(shí)間之后媚朦,可能會(huì)變成:
//div[@class="preview green"]/a/img
數(shù)據(jù)指向的class優(yōu)于排版指向的class
在上一個(gè)例子中,使用thumbnail和green兩個(gè)class都不好日戈。thumbnail比green好询张,但這兩個(gè)都不如departure-time。前面兩個(gè)是用來(lái)排版的浙炼,departure-time是有語(yǔ)義的份氧,和div中的內(nèi)容有關(guān)。所以弯屈,在排版發(fā)生改變的情況下蜗帜,departure-time發(fā)生改變的可能性會(huì)比較小。應(yīng)該說资厉,網(wǎng)站作者在開發(fā)中十分清楚钮糖,為內(nèi)容設(shè)置有意義的、一致的標(biāo)記酌住,可以讓開發(fā)過程收益。id通常是最可靠的
只要id具有語(yǔ)義并且數(shù)據(jù)相關(guān)阎抒,id通常是抓取時(shí)最好的選擇酪我。部分原因是,JavaScript和外鏈錨點(diǎn)總是使用id獲取文檔中特定的部分且叁。例如都哭,下面的XPath非常可靠:
//*[@id="more_info"]//text( )
相反的例子是,指向唯一參考的id欺矫,對(duì)抓取沒什么幫助纱新,因?yàn)樽ト】偸窍M軌颢@取具有某個(gè)特點(diǎn)的所有信息。例如:
//[@id="order-F4982322"]
這是一個(gè)非常差的XPath表達(dá)式穆趴。還要記住脸爱,盡管id最好要有某種特點(diǎn),但在許多HTML文檔中未妹,id都很雜亂無(wú)章簿废。
總結(jié)
編程語(yǔ)言的不斷進(jìn)化,使得創(chuàng)建可靠的XPath表達(dá)式從HTML抓取信息變得越來(lái)越容易络它。在本章中族檬,你學(xué)到了HTML和XPath的基本知識(shí)、如何利用Chrome自動(dòng)獲取XPath表達(dá)式化戳。你還學(xué)會(huì)了如何手工寫XPath表達(dá)式单料,并區(qū)分可靠和不夠可靠的XPath表達(dá)式。第3章中点楼,我們會(huì)用這些知識(shí)來(lái)寫幾個(gè)爬蟲扫尖。
序言
第1章 Scrapy介紹
第2章 理解HTML和XPath
第3章 爬蟲基礎(chǔ)
第4章 從Scrapy到移動(dòng)應(yīng)用
第5章 快速構(gòu)建爬蟲
第6章 Scrapinghub部署
第7章 配置和管理
第8章 Scrapy編程
第9章 使用Pipeline
第10章 理解Scrapy的性能
第11章(完) Scrapyd分布式抓取和實(shí)時(shí)分析