天天看點

Python網絡爬蟲2 ---- scrapy爬蟲架構介紹和初試

上一篇文章的環境搭建是相對于手動操作的過程,而大家可能對這個疑問是什麼是scrapy?為什麼要用scrapy?下面主要是對這兩個問題的簡要回答。

相信大家在百度或google上一搜尋scrapy都能夠找到一大堆的結果,由于我本人對scrapy沒有太了解,是以這裡我也是引用了網絡上的說法對scrapy進行說明,也希望各位能夠多指點一下小弟。

========================華麗的分割線========================

Scrapy

是一套基于Twisted的異步處理架構,是純python實作的爬蟲架構,使用者隻需要定制開發幾個子產品就可以輕松的實作一個爬蟲,用來抓取網頁内容或者各種圖檔。下圖顯示了Scrapy的大體架構,其中包含了scheduler、item pipeline、downloader、spider以及engine這幾個元件子產品,而其中的綠色箭頭則說明了整套系統的資料處理流程。

Python網絡爬蟲2 ---- scrapy爬蟲架構介紹和初試

下面就來一個個解釋每個元件的作用及資料的處理過程。

一、元件說明:

    1、Scrapy Engine(Scrapy引擎)

    Scrapy引擎是用來控制整個系統的資料處理流程,并進行事務處理的觸發。更多的詳細内容可以看下面的資料處理流程。

    2、Scheduler(排程)

    排程程式從Scrapy引擎接受請求并排序列入隊列,并在Scrapy引擎送出請求後返還給他們。

    3、Downloader(下載下傳器)

    下載下傳器的主要職責是抓取網頁并将網頁内容返還給蜘蛛( Spiders)。

    4、Spiders(蜘蛛)

    蜘蛛是有Scrapy使用者自己定義用來解析網頁并抓取制定URL傳回的内容的類,每個蜘蛛都能處理一個域名或一組域名。換句話說就是用來定義特定網站的抓取和解析規則。

    蜘蛛的整個抓取流程(周期)是這樣的:

    (1)首先擷取第一個URL的初始請求,當請求傳回後調取一個回調函數。第一個請求是通過調用start_requests()方法。該方法預設從start_urls中的Url中生成請求,并執行解析來調用回調函數。

    (2)在回調函數中,你可以解析網頁響應并傳回項目對象和請求對象或兩者的疊代。這些請求也将包含一個回調,然後被Scrapy下載下傳,然後有指定的回調處理。

    (3)在回調函數中,你解析網站的内容,同程使用的是Xpath選擇器(但是你也可以使用BeautifuSoup, lxml或其他任何你喜歡的程式),并生成解析的資料項。

    (4)最後,從蜘蛛傳回的項目通常會進駐到項目管道。

5、Item Pipeline(項目管道)

    項目管道的主要責任是負責處理有蜘蛛從網頁中抽取的項目,他的主要任務是清晰、驗證和存儲資料。當頁面被蜘蛛解析後,将被發送到項目管道,并經過幾個特定的次序處理資料。每個項目管道的元件都是有一個簡單的方法組成的Python類。他們擷取了項目并執行他們的方法,同時他們還需要确定的是是否需要在項目管道中繼續執行下一步或是直接丢棄掉不處理。

項目管道通常執行的過程有:

清洗HTML資料

驗證解析到的資料(檢查項目是否包含必要的字段)

檢查是否是重複資料(如果重複就删除)

将解析到的資料存儲到資料庫中

6、Downloader middlewares(下載下傳器中間件)

    下載下傳中間件是位于Scrapy引擎和下載下傳器之間的鈎子架構,主要是處理Scrapy引擎與下載下傳器之間的請求及響應。它提供了一個自定義的代碼的方式來拓展 Scrapy的功能。下載下傳中間器是一個處理請求和響應的鈎子架構。他是輕量級的,對Scrapy盡享全局控制的底層的系統。

7、Spider middlewares(蜘蛛中間件)

    蜘蛛中間件是介于Scrapy引擎和蜘蛛之間的鈎子架構,主要工作是處理蜘蛛的響應輸入和請求輸出。它提供一個自定義代碼的方式來拓展Scrapy的功能。蛛中間件是一個挂接到Scrapy的蜘蛛處理機制的架構,你可以插入自定義的代碼來處理發送給蜘蛛的請求和傳回蜘蛛擷取的響應内容和項目。

8、Scheduler middlewares(排程中間件)

    排程中間件是介于Scrapy引擎和排程之間的中間件,主要工作是處從Scrapy引擎發送到排程的請求和響應。他提供了一個自定義的代碼來拓展Scrapy的功能。

二、資料處理流程

Scrapy的整個資料處理流程由Scrapy引擎進行控制,其主要的運作方式為:

引擎打開一個域名,時蜘蛛處理這個域名,并讓蜘蛛擷取第一個爬取的URL。

引擎從蜘蛛那擷取第一個需要爬取的URL,然後作為請求在排程中進行排程。

引擎從排程那擷取接下來進行爬取的頁面。

排程将下一個爬取的URL傳回給引擎,引擎将他們通過下載下傳中間件發送到下載下傳器。

當網頁被下載下傳器下載下傳完成以後,響應内容通過下載下傳中間件被發送到引擎。

引擎收到下載下傳器的響應并将它通過蜘蛛中間件發送到蜘蛛進行處理。

蜘蛛處理響應并傳回爬取到的項目,然後給引擎發送新的請求。

引擎将抓取到的項目項目管道,并向排程發送請求。

系統重複第二部後面的操作,直到排程中沒有請求,然後斷開引擎與域之間的聯系。

以上部分是屬于網上抄過來的,誰是第一手就無可考究了,對于大家是否能看明白就沒有保證了,我本還還算能夠明白七成吧。

上面兩個分割線中的内容也算得是回答了第一個問題了,那麼為什麼要用scrapy呢?我有一個習慣,在提出問題的時候都會先再找一個問題,對于這個問題而提出的問題是:我自己寫一個不行嗎?

剛開始的時候我也是這樣想的,于是就自己開始找python怎麼抓網頁資料之類的了,後來還弄了一個不堪入目的一段代碼,用于從一個根網頁中找到所有的連結,然後将這些連結都放到一個清單中,然後弄個循環從這個清單中一個個去抓。下面就是我之前第一次接觸python時寫的代碼(不堪入目,不喜勿噴。。。)

<code>001</code>

<code>#encoding=utf-8</code>

<code>002</code>

<code>003</code>

<code>__author__ </code><code>=</code> <code>'dragon'</code>

<code>004</code>

<code>005</code>

<code>import</code> <code>urllib2</code>

<code>006</code>

<code>import</code> <code>os</code>

<code>007</code>

<code>import</code> <code>pymongo</code>

<code>008</code>

<code>import</code> <code>time</code>

<code>009</code>

<code>import</code> <code>hashlib</code>

<code>010</code>

<code>011</code>

<code>def</code> <code>myspider(startweb, keyword):</code>

<code>012</code>

<code>    </code><code>list</code> <code>=</code> <code>[startweb]</code>

<code>013</code>

<code>    </code><code>curindex </code><code>=</code> <code>0</code>

<code>014</code>

<code>    </code><code>Keyword </code><code>=</code> <code>keyword</code>

<code>015</code>

<code>016</code>

<code>    </code><code>#網絡上MongoHQ</code>

<code>017</code>

<code>    </code><code>#con = pymongo.Connection("paulo.mongohq.com", 10042)</code>

<code>018</code>

<code>    </code><code>#db = con.mytest</code>

<code>019</code>

<code>    </code><code>#db.authenticate("dragon", "dragon")</code>

<code>020</code>

<code>    </code><code>#db.urllist.drop()</code>

<code>021</code>

<code>022</code>

<code>    </code><code>#本地資料庫</code>

<code>023</code>

<code>    </code><code>con </code><code>=</code> <code>pymongo.Connection(</code><code>"localhost"</code><code>, </code><code>27017</code><code>)</code>

<code>024</code>

<code>    </code><code>db </code><code>=</code> <code>con.mytest</code>

<code>025</code>

<code>026</code>

<code>    </code><code>while</code> <code>curindex &lt; </code><code>len</code><code>(</code><code>list</code><code>):</code>

<code>027</code>

<code>        </code><code>url </code><code>=</code> <code>list</code><code>[curindex]</code>

<code>028</code>

<code>        </code><code>print</code> <code>"list count ="</code><code>, </code><code>len</code><code>(</code><code>list</code><code>), </code><code>"  curcheck "</code><code>, curindex</code>

<code>029</code>

<code>        </code><code>print</code> <code>"try to visit "</code><code>, url</code>

<code>030</code>

<code>031</code>

<code>        </code><code>headers </code><code>=</code> <code>(</code><code>'User-Agent'</code><code>, </code><code>'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36'</code><code>)</code>

<code>032</code>

<code>033</code>

<code>        </code><code>try</code><code>:</code>

<code>034</code>

<code>            </code><code>opener </code><code>=</code> <code>urllib2.build_opener()</code>

<code>035</code>

<code>            </code><code>opener.addheaders </code><code>=</code> <code>[headers]</code>

<code>036</code>

<code>            </code><code>openness </code><code>=</code> <code>opener.</code><code>open</code><code>(url, </code><code>None</code><code>, </code><code>30</code><code>)</code>

<code>037</code>

<code>            </code><code>data </code><code>=</code> <code>openness.read()</code>

<code>038</code>

<code>            </code><code>opener.close()</code>

<code>039</code>

<code>        </code><code>except</code><code>:</code>

<code>040</code>

<code>            </code><code>print</code> <code>"some error ..."</code>

<code>041</code>

<code>            </code><code>curindex </code><code>+</code><code>=</code> <code>1</code>

<code>042</code>

<code>            </code><code>continue</code>

<code>043</code>

<code>044</code>

<code>        </code><code>print</code> <code>"finish get data..."</code>

<code>045</code>

<code>046</code>

<code>        </code><code>os.remove(</code><code>"d:/test.txt"</code><code>)</code>

<code>047</code>

<code>        </code><code>file</code> <code>=</code> <code>open</code><code>(</code><code>"d:/test.txt"</code><code>, </code><code>"a"</code><code>)</code>

<code>048</code>

<code>        </code><code>print</code> <code>&gt;&gt; </code><code>file</code><code>, data</code>

<code>049</code>

<code>        </code><code>file</code><code>.close()</code>

<code>050</code>

<code>051</code>

<code>        </code><code>myfile      </code><code>=</code> <code>open</code><code>(</code><code>"d:/test.txt"</code><code>, </code><code>"r"</code><code>)</code>

<code>052</code>

<code>        </code><code>mystring    </code><code>=</code> <code>myfile.read()</code>

<code>053</code>

<code>        </code><code>myfile.close()</code>

<code>054</code>

<code>055</code>

<code>        </code><code>#找到标題</code>

<code>056</code>

<code>        </code><code>title       </code><code>=</code> <code>""</code>

<code>057</code>

<code>        </code><code>headstart   </code><code>=</code> <code>mystring.find(</code><code>"&lt;head&gt;"</code><code>)</code>

<code>058</code>

<code>        </code><code>headend     </code><code>=</code> <code>mystring.find(</code><code>"&lt;/head&gt;"</code><code>)</code>

<code>059</code>

<code>        </code><code>if</code> <code>headstart &lt; </code><code>0</code><code>:</code>

<code>060</code>

<code>            </code><code>headstart   </code><code>=</code> <code>mystring.find(</code><code>"&lt;HEAD&gt;"</code><code>)</code>

<code>061</code>

<code>            </code><code>headend     </code><code>=</code> <code>mystring.find(</code><code>"&lt;/HEAD&gt;"</code><code>)</code>

<code>062</code>

<code>063</code>

<code>        </code><code>if</code> <code>headstart &gt; </code><code>0</code><code>:</code>

<code>064</code>

<code>            </code><code>titlestart  </code><code>=</code> <code>mystring.find(</code><code>"&lt;title&gt;"</code><code>)</code>

<code>065</code>

<code>            </code><code>titleend    </code><code>=</code> <code>mystring.find(</code><code>"&lt;/title&gt;"</code><code>)</code>

<code>066</code>

<code>            </code><code>if</code> <code>titlestart &lt; </code><code>0</code><code>:</code>

<code>067</code>

<code>                </code><code>titlestart  </code><code>=</code> <code>mystring.find(</code><code>"&lt;TITLE&gt;"</code><code>)</code>

<code>068</code>

<code>                </code><code>titleend    </code><code>=</code> <code>mystring.find(</code><code>"&lt;/TITLE&gt;"</code><code>)</code>

<code>069</code>

<code>070</code>

<code>            </code><code>if</code> <code>titleend &gt; titlestart </code><code>and</code> <code>titlestart &lt; headend </code><code>and</code> <code>titleend &lt; headend:</code>

<code>071</code>

<code>                </code><code>title </code><code>=</code> <code>mystring[titlestart</code><code>+</code><code>len</code><code>(</code><code>"&lt;title&gt;"</code><code>):titleend]</code>

<code>072</code>

<code>073</code>

<code>        </code><code>dbdata </code><code>=</code> <code>{</code><code>"title"</code><code>:"</code><code>", "</code><code>url</code><code>":"</code><code>", "</code><code>time</code><code>":"</code><code>"}</code>

<code>074</code>

<code>075</code>

<code>076</code>

<code>            </code><code>title </code><code>=</code> <code>title.decode(</code><code>"utf-8"</code><code>).encode(</code><code>"utf-8"</code><code>)</code>

<code>077</code>

<code>078</code>

<code>            </code><code>try</code><code>:</code>

<code>079</code>

<code>                </code><code>title </code><code>=</code> <code>title.decode(</code><code>"gbk"</code><code>).encode(</code><code>"utf-8"</code><code>)</code>

<code>080</code>

<code>            </code><code>except</code><code>:</code>

<code>081</code>

<code>                </code><code>pass</code>

<code>082</code>

<code>083</code>

<code>084</code>

<code>        </code><code>dbdata[</code><code>"title"</code><code>] </code><code>=</code> <code>title</code>

<code>085</code>

<code>        </code><code>dbdata[</code><code>"url"</code><code>] </code><code>=</code> <code>url</code>

<code>086</code>

<code>        </code><code>dbdata[</code><code>"time"</code><code>] </code><code>=</code> <code>time.strftime(</code><code>'%Y-%m-%d %H:%M:%S'</code><code>, time.localtime(time.time()))</code>

<code>087</code>

<code>088</code>

<code>            </code><code>db.urllist.insert(dbdata)</code>

<code>089</code>

<code>090</code>

<code>            </code><code>print</code> <code>"insert error"</code>

<code>091</code>

<code>092</code>

<code>        </code><code>if</code> <code>len</code><code>(mystring) &gt; </code><code>0</code><code>:</code>

<code>093</code>

<code>            </code><code>while</code> <code>len</code><code>(mystring) &gt; </code><code>0</code><code>:</code>

<code>094</code>

<code>                </code><code>start </code><code>=</code> <code>mystring.find(</code><code>"href=\""</code><code>)</code>

<code>095</code>

<code>                </code><code>if</code> <code>start &lt;</code><code>=</code> <code>0</code><code>:</code>

<code>096</code>

<code>                    </code><code>break</code>

<code>097</code>

<code>098</code>

<code>                </code><code>substring </code><code>=</code> <code>mystring[start</code><code>+</code><code>6</code><code>:]</code>

<code>099</code>

<code>                </code><code>end </code><code>=</code> <code>substring.find(</code><code>"\""</code><code>)</code>

<code>100</code>

<code>                </code><code>weblink </code><code>=</code> <code>substring[:end]</code>

<code>101</code>

<code>                </code><code>if</code> <code>Keyword !</code><code>=</code> <code>"":</code>

<code>102</code>

<code>                    </code><code>if</code> <code>weblink.find(Keyword) &gt;</code><code>=</code> <code>0</code> <code>and</code> <code>list</code><code>.count(weblink) &lt;</code><code>=</code> <code>0</code><code>:</code>

<code>103</code>

<code>                        </code><code>list</code><code>.append(weblink)</code>

<code>104</code>

<code>                </code><code>elif</code> <code>0</code> <code>&gt; weblink.find(</code><code>"video.sina.com.cn"</code><code>) \</code>

<code>105</code>

<code>                    </code><code>and</code> <code>0</code> <code>&gt; weblink.find(</code><code>"video.baidu.com"</code><code>) \</code>

<code>106</code>

<code>                    </code><code>and</code> <code>0</code> <code>&lt;</code><code>=</code> <code>weblink.find(</code><code>"http:"</code><code>) \</code>

<code>107</code>

<code>                    </code><code>and</code> <code>0</code> <code>&gt;</code><code>=</code> <code>list</code><code>.count(weblink):</code>

<code>108</code>

<code>109</code>

<code>                    </code><code>list</code><code>.append(weblink)</code>

<code>110</code>

<code>111</code>

<code>                </code><code>mystring </code><code>=</code> <code>mystring[start</code><code>+</code><code>6</code><code>:]</code>

<code>112</code>

<code>113</code>

<code>        </code><code>curindex </code><code>+</code><code>=</code> <code>1</code>

<code>114</code>

<code>115</code>

<code>if</code> <code>__name__ </code><code>=</code><code>=</code> <code>'__main__'</code><code>:</code>

<code>116</code>

<code>    </code><code>myspider(</code><code>"http://www.hao123.com"</code><code>, </code><code>"hao123"</code><code>)</code>

處理的流程是:

    1. 将一個開始的網頁url存放到list中

    2. 不斷從list中取出url進行資料擷取

    3. 在擷取到的網頁資料中的連結都存放到list裡面

    4. 不斷重複2、3步驟

上面這段代碼寫得怎麼樣就不值得評論了,但是值得我們思考的是,上面的代碼出發點是爬蟲,并且還是漫無目的的爬蟲,沒有了終點,或者大家會想到很多退出循環的方法,但是我們還有很多問題需要考慮:如何提高爬蟲的效率?如何最大限度利用網絡帶寬?如果提高抓取回來資料的處理?最重要一點是:我們都不會想着去做google或百度,而是針對一些特定的需求來實作一個爬蟲,那麼我們如何簡單而又快速的去實作我們的定制功能呢?

上面的幾個問題就是我們最終都會遇到的問題,也是scrapy能夠很好的處理的問題,它通過幾個元件完成不同的部分,将類似下載下傳網頁資料的這些通用操作封裝起來,減少了我們編寫爬蟲時的難度,并且各個部件之間通過異步來處理,能夠最大限度利用了網絡帶寬。我們隻需要按照它的要求來實作幾個子產品就可以了。

最後,講講使用如何生成一個scrapy工程。

打開cmd,cd到你要建立工程的目錄,然後使用以下指令建立工程test:

    scrapy startproject test

如下圖所示,我們看到建立了一個test檔案夾,裡面包含了其他一些檔案

Python網絡爬蟲2 ---- scrapy爬蟲架構介紹和初試

根據上圖可以對照第一幅圖,找到一些元件的對應檔案,我們可以在spider下面建立一個py檔案(例如:spider.py),然後寫上以下代碼:

<code>1</code>

<code>from</code> <code>scrapy.spider </code><code>import</code> <code>BaseSpider</code>

<code>2</code>

<code>3</code>

<code>class</code> <code>test(BaseSpider):</code>

<code>4</code>

<code>    </code><code>name </code><code>=</code> <code>"test"</code>

<code>5</code>

<code>    </code><code>allowed_domains </code><code>=</code> <code>[</code><code>"hao123.com"</code><code>]</code>

<code>6</code>

<code>    </code><code>start_urls </code><code>=</code> <code>[</code><code>"http://www.hao123.com"</code><code>]</code>

<code>7</code>

<code>8</code>

<code>    </code><code>def</code> <code>parse(</code><code>self</code><code>, response):</code>

<code>9</code>

<code>        </code><code>print</code> <code>response.url</code>

在cmd中,cd進入剛才建立的test目錄,使用以下指令運作這個爬蟲,我們可以最後看到一些debug輸出

Python網絡爬蟲2 ---- scrapy爬蟲架構介紹和初試
Python網絡爬蟲2 ---- scrapy爬蟲架構介紹和初試

這篇文章到此為止,寫得比較粗糙。可能大家對于spider.py中的一些變量和函數名稱,以及該子產品什麼時候被調用存在疑問,大家可以上網找找資料學習,我将在下一篇檔案中說明,并且以擷取百度文庫中的圖書資訊作為例子,完成相關的代碼并提供源碼。