Python scrapy 安裝與開發

Scrapy是採用Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取採集web站點信息並從頁面中提取結構化的數據。

Scrapy用途廣泛,可以用於數據挖掘、監測和自動化測試等。

Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。

它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支持。

Scrapy is a fast high-level web crawling and web scraping framework, used to crawl websites and extract structured data from their pages.

It can be used for a wide range of purposes, from data mining to monitoring and automated testing.

For more information including a list of features check the Scrapy homepage at: http://scrapy.org

Scrapy 官網https://scrapy.org

Scrapy 插件https://pypi.python.org/pypi/Scrapy

 

Scratch,是抓取的意思,這個Python的爬蟲框架叫Scrapy,大概也是這個意思吧,就叫它:小刮刮吧。

Scrapy 使用了 Twisted異步網絡庫來處理網絡通訊,其設計整體架構大致如下圖:

python-scrapy-an-zhuang-yu-kai-fa-01

Scrapy主要包括了以下組件:

  1. 引擎(Scrapy)
    用來處理整個系統的數據流處理, 觸發事務(框架核心)
  2. 調度器(Scheduler)
    用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址
  3. 下載器(Downloader)
    用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)
  4. 爬蟲(Spiders)
    爬蟲是主要幹活的, 用於從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
  5. 項目管道(Pipeline)
    負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並經過幾個特定的次序處理數據。
  6. 下載器中間件(Downloader Middlewares)
    位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
  7. 爬蟲中間件(Spider Middlewares)
    介於Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。
  8. 調度中間件(Scheduler Middewares)
    介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。

Scrapy運行流程大概如下:

  1. 引擎從調度器中取出一個鏈接(URL)用於接下來的抓取
  2. 引擎把URL封裝成一個請求(Request)傳給下載器
  3. 下載器把資源下載下來,並封裝成應答包(Response)
  4. 爬蟲解析Response
  5. 解析出實體(Item),則交給實體管道進行進一步的處理
  6. 解析出的是鏈接(URL),則把URL交給調度器等待抓取

 

Scrapy 安裝

因爲python3並不能完全支持Scrapy,因此爲了完美運行Scrapy,我們使用python2.7來編寫和運行Scrapy。

pip install Scrapy          # python2.7
pip3 install Scrapy        # python3.6

驗證安裝成功:

1
2
3
4
5
6
7
$ python
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 12:39:47)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help" , "copyright" , "credits" or "license" for more information.
>>> import scrapy
>>> scrapy
<module 'scrapy' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/scrapy/__init__.pyc' >

 

Scrapy 使用

1、創建項目 mimvp_proxy_python_scrapy

運行命令:

1
2
3
4
5
$ which scrapy
/Library/Frameworks/Python .framework /Versions/3 .6 /bin/scrapy
$
$ cd MimvpProxyDemo /PythonScrapy/
$ scrapy startproject mimvp_proxy_python_scrapy  

 

2、查看目錄結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ tree
.
|____mimvp_proxy_python_scrapy
| |____mimvp_proxy_python_scrapy
| | |______init__.py
| | |______pycache__
| | |____items.py
| | |____middlewares.py
| | |____pipelines.py
| | |____settings.py
| | |____spiders
| | | |______init__.py
| | | |______pycache__
| |____scrapy.cfg

 

導入Eclipse,查看自動創建的目錄結構:

python-scrapy-an-zhuang-yu-kai-fa-02

目錄文件說明:

  • scrapy.cfg  項目的配置信息,主要爲Scrapy命令行工具提供一個基礎的配置信息
  • items.py    設置數據存儲模板,用於結構化數據,如:Django的Model
  • pipelines    數據處理行爲,如:一般結構化的數據持久化
  • settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等爬蟲相關的配置
  • spiders      爬蟲目錄,如:創建文件、編寫爬蟲規則

注意:一般創建爬蟲文件時,以網站域名命名

 

3、Scrapy 編寫爬蟲

在spiders目錄中新建 mimvp_spider.py 文件

示例代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# mimvp.com
# 2009.10.1
 
 
import scrapy
  
class MimvpSpider(scrapy.spiders.Spider):
     name = "mimvp"
     allowed_domains = [ "mimvp.com" ]
     start_urls = [
         "http://mimvp.com" ,
     ]
  
     def parse( self , response):
         # print(response, type(response))
         # from scrapy.http.response.html import HtmlResponse
         # print(response.body_as_unicode())
  
         mimvp_url = response.url                    # 爬取時請求的url
         body = response.body                        # 返回網頁內容
         unicode_body = response.body_as_unicode()   # 返回的html unicode編碼
         
         print ( "mimvp_url : " + str (mimvp_url))
         print ( "body : " + str (body))
         print ( "unicode_body : " + str (unicode_body))

爲了創建一個Spider,必須繼承 scrapy.Spider 類, 且定義以下三個屬性:

  • name: 用於區別Spider。 該名字必須是唯一的,您不可以爲不同的Spider設定相同的名字。
  • start_urls: 包含了Spider在啓動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 後續的URL則從初始的URL獲取到的數據中提取。
  • parse() 是spider的一個方法。 被調用時,每個初始URL完成下載後生成的 Response 對象將會作爲唯一的參數傳遞給該函數。 該方法負責解析返回的數據(response data),提取數據(生成item)以及生成需要進一步處理的URL的 Request 對象。

詳細說明:

1. 爬蟲文件需要定義一個類,並繼承scrapy.spiders.Spider,即類定義 MimvpSpider(scrapy.spiders.Spider)

2. 必須定義name,即爬蟲名,如果沒有name,會報錯,因爲源碼中是這樣定義的:

1
2
3
4
5
6
7
8
def __init__( self , name = None , * * kwargs):
     if name is not None :
         self .name = name
     elif not getattr ( self , 'name' , None ):
         raise ValueError( "%s must have a name" % type ( self ).__name__)
     self .__dict__.update(kwargs)
     if not hasattr ( self , 'start_urls' ):
         self .start_urls = []

3. 編寫函數parse,這裏需要注意的是,該函數名不能改變,因爲Scrapy源碼中默認callback函數的函數名就是parse;

4. 定義需要爬取的url,放在列表中,因爲可以爬取多個url,Scrapy源碼是一個For循環,從上到下爬取這些url,使用生成器迭代將url發送給下載器下載url的html。源碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
def start_requests( self ):
     cls = self .__class__
     if method_is_overridden( cls , Spider, 'make_requests_from_url' ):
         for url in self .start_urls:
             yield self .make_requests_from_url(url)
     else :
         for url in self .start_urls:
             yield Request(url, dont_filter = True )
 
def make_requests_from_url( self , url):
     """ This method is deprecated. """
     return Request(url, dont_filter = True )

 

Request 類源碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Request(object_ref):
 
     def __init__( self , url, callback = None , method = 'GET' , headers = None , body = None ,
                  cookies = None , meta = None , encoding = 'utf-8' , priority = 0 ,
                  dont_filter = False , errback = None , flags = None ):
 
         self ._encoding = encoding  # this one has to be set first
         self .method = str (method).upper()
         self ._set_url(url)
         self ._set_body(body)
         assert isinstance (priority, int ), "Request priority not an integer: %r" % priority
         self .priority = priority
 
         assert callback or not errback, "Cannot use errback without a callback"
         self .callback = callback
         self .errback = errback
 
         self .cookies = cookies or {}
         self .headers = Headers(headers or {}, encoding = encoding)
         self .dont_filter = dont_filter
 
         self ._meta = dict (meta) if meta else None
         self .flags = [] if flags is None else list (flags)

 

4、Scrapy 運行

格式:scrapy crawl + 爬蟲名  –nolog    # 不顯示日誌

進入項目 mimvp_proxy_python_scrapy 目錄下,運行命令:

1
2
$ cd mimvp_proxy_python_scrapy/
$ scrapy crawl mimvp --nolog

運行結果:

$ scrapy crawl mimvp --nolog
mimvp_url : http://mimvp.com
body : b'<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n<meta charset="utf-8">\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">\n<meta http-equiv="Cache-Control" content="no-transform">

..........

 

5、Scrapy 查詢語法

當我們爬取大量的網頁,如果自己寫正則匹配,會很麻煩,也很浪費時間,令人欣慰的是,scrapy內部支持更簡單的查詢語法,幫助我們去html中查詢我們需要的標籤和標籤內容以及標籤屬性。

下面逐一進行介紹:

  • 查詢子子孫孫中的某個標籤(以div標籤爲例)://div
  • 查詢兒子中的某個標籤(以div標籤爲例):/div
  • 查詢標籤中帶有某個class屬性的標籤://div[@class=’c1′]即子子孫孫中標籤是div且class=‘c1’的標籤
  • 查詢標籤中帶有某個class=‘c1’並且自定義屬性name=‘alex’的標籤://div[@class=’c1′][@name=’alex’]
  • 查詢某個標籤的文本內容://div/span/text() 即查詢子子孫孫中div下面的span標籤中的文本內容
  • 查詢某個屬性的值(例如查詢a標籤的href屬性)://a/@href

示例代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def parse( self , response):
        # 分析頁面
        # 找到頁面中符合規則的內容(校花圖片),保存
        # 找到所有的a標籤,再訪問其他a標籤,一層一層的搞下去
  
        hxs = HtmlXPathSelector(response) #創建查詢對象
  
        # 如果url是 http://www.xiaohuar.com/list-1-\d+.html
        if re.match( 'http://www.xiaohuar.com/list-1-\d+.html' , response.url): #如果url能夠匹配到需要爬取的url,即本站url
            items = hxs.select( '//div[@class="item_list infinite_scroll"]/div' ) #select中填寫查詢目標,按scrapy查詢語法書寫
            for i in range ( len (items)):
                src = hxs.select( '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/a/img/@src' % i).extract() #查詢所有img標籤的src屬性,即獲取校花圖片地址
                name = hxs.select( '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/span/text()' % i).extract() #獲取span的文本內容,即校花姓名
                school = hxs.select( '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()' % i).extract() #校花學校
                if src:
                    ab_src = "http://www.xiaohuar.com" + src[ 0 ] #相對路徑拼接
                    file_name = "%s_%s.jpg" % (school[ 0 ].encode( 'utf-8' ), name[ 0 ].encode( 'utf-8' )) #文件名,因爲python27默認編碼格式是unicode編碼,因此我們需要編碼成utf-8
                    file_path = os.path.join( "/Users/wupeiqi/PycharmProjects/beauty/pic" , file_name)
                    urllib.urlretrieve(ab_src, file_path)

注:urllib.urlretrieve(ab_src, file_path) ,接收文件路徑和需要保存的路徑,會自動去文件路徑下載並保存到我們指定的本地路徑。

 

6、遞歸爬取網頁

上述代碼僅僅實現了一個url的爬取,如果該url的爬取的內容中包含了其他url,而我們也想對其進行爬取,那麼如何實現遞歸爬取網頁呢?

示例代碼:

1
2
3
4
5
# 獲取所有的url,繼續訪問,並在其中尋找相同的url
         all_urls = hxs.select( '//a/@href' ).extract()
         for url in all_urls:
             if url.startswith( 'http://www.xiaohuar.com/list-1-' ):
                 yield Request(url, callback = self .parse)

即通過yield生成器向每一個url發送request請求,並執行返回函數parse,從而遞歸獲取校花圖片和校花姓名學校等信息。

注:可以修改settings.py 中的配置文件,以此來指定「遞歸」的層數,如: DEPTH_LIMIT = 1

 

7、Scrapy 設置代理爬取網頁

Python Scrapy 設置代理有兩種方式,使用時兩種方式選擇一種即可

方式1: 直接在代碼裏設置,如 MimvpSpider ——> start_requests

方式2: 通過 middlewares + settings.py 配置文件設置,步驟:

                    2.1 middlewares.py 添加代理類 ProxyMiddleware,並添加代理

                    2.2 settings.py 開啓 DOWNLOADER_MIDDLEWARES,並且添加 'mimvp_proxy_python_scrapy.middlewares.ProxyMiddleware': 100, 

 

方式1:直接在代碼裏設置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# Python scrapy 支持 http、https
#
# 米撲代理示例:
# http://proxy.mimvp.com/demo2.php
#
# 米撲代理購買:
# http://proxy.mimvp.com
#
# mimvp.com
# 2009.10.1
 
# Python Scrapy 設置代理有兩種方式,使用時兩種方式選擇一種即可
# 方式1: 直接在代碼裏設置,如 MimvpSpider ——> start_requests
# 方式2: 通過 middlewares + settings.py 配置文件設置,步驟:
#        2.1 middlewares.py 添加代理類 ProxyMiddleware,並添加代理
#        2.2 settings.py 開啓 DOWNLOADER_MIDDLEWARES,並且添加 'mimvp_proxy_python_scrapy.middlewares.ProxyMiddleware': 100,
 
 
import scrapy
  
class MimvpSpider(scrapy.spiders.Spider):
     name = "mimvp"
     allowed_domains = [ "mimvp.com" ]
     start_urls = [
         "http://proxy.mimvp.com/exist.php" ,
         "https://proxy.mimvp.com/exist.php" ,
     ]
  
     ## 代理設置方式1:直接在代理裏設置
     def start_requests( self ):
         urls = [
             "http://proxy.mimvp.com/exist.php" ,
             "https://proxy.mimvp.com/exist.php" ,
         ]
         for url in urls:
             meta_proxy = ""
             if url.startswith( "http://" ):
                 meta_proxy = "http://180.96.27.12:88"           # http代理
             elif url.startswith( "https://" ):
                 meta_proxy = "http://109.108.87.136:53281"      # https代理
                 
             yield scrapy.Request(url = url, callback = self .parse, meta = { 'proxy' : meta_proxy})
 
 
     def parse( self , response):
         mimvp_url = response.url                    # 爬取時請求的url
         body = response.body                        # 返回網頁內容
         
         print ( "mimvp_url : " + str (mimvp_url))
         print ( "body : " + str (body))

 

方式2:配置文件裏設置

a) middlewares.py 文件裏,添加代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 代理設置方式2: 通過 middlewares + settings.py 配置文件設置
## mimvp custom by yourself
class ProxyMiddleware( object ):
     def process_request( self ,request,spider):
     
         if request.url.startswith( "http://" ):
             request.meta[ 'proxy'
相關文章
相關標籤/搜索