爬蟲之scrapy框架

解析

  Scrapy解釋

Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 其能夠應用在數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
其最初是爲了頁面抓取 (更確切來講, 網絡抓取 )所設計的, 也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。html

Scrapy 使用了 Twisted異步網絡庫來處理網絡通信。總體架構大體以下python

  Scrapy組件

  ①引擎(Scrapy)正則表達式

用來處理整個系統的數據流處理, 觸發事務(框架核心)

  ②調度器(Scheduler)數據庫

用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL(抓取網頁的網址或者說是連接)的優先隊列, 
由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址

  ③下載器(Downloader)json

用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是創建在twisted這個高效的異步模型上的)

  ④爬蟲(Spiders)windows

爬蟲是主要幹活的, 用於從特定的網頁中提取本身須要的信息, 即所謂的實體(Item)。用戶也能夠從中提取出連接,讓Scrapy繼續抓取下一個頁面

  ⑤項目管道(Pipeline)api

負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證明體的有效性、清除不須要的信息。當頁面被爬蟲解析後,
將被髮送到項目管道,並通過幾個特定的次序處理數據。

  ⑥下載器中間件(Downloader Middlewares)cookie

位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。

  ⑦爬蟲中間件(Spider Middlewares)網絡

介於Scrapy引擎和爬蟲之間的框架,主要工做是處理蜘蛛的響應輸入和請求輸出。

  ⑧調度中間件(Scheduler Middewares)架構

介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。

  Scrapy運行流程

  1. 引擎從調度器中取出一個連接(URL)用於接下來的抓取

  2. 引擎把URL封裝成一個請求(Request)傳給下載器

  3. 下載器把資源下載下來,並封裝成應答包(Response)

  4. 爬蟲解析Response

  5. 解析出實體(Item),則交給實體管道進行進一步的處理

  6. 解析出的是連接(URL),則把URL交給調度器等待抓取

安裝

在python3並不能徹底支持Scrapy,所以爲了完美運行Scrapy,咱們使用python2.7來編寫和運行Scrapy。

pip install Scrapy

 

  注:windows平臺須要依賴pywin32,請根據本身系統32/64位選擇下載安裝,https://sourceforge.net/projects/pywin32/

    其它可能依賴的安裝包:lxml-3.6.4-cp27-cp27m-win_amd64.whl,VCForPython27.msi

依賴包下載:http://pan.baidu.com/s/1eSdVdx4

使用

 

  建立項目

運行命令:

scrapy startproject fuck  # fuck這是我起的項目名

項目建立後會自動建立幾個目錄

文件說明:

  • scrapy.cfg  項目的配置信息,主要爲Scrapy命令行工具提供一個基礎的配置信息。(真正爬蟲相關的配置信息在settings.py文件中)

  • items.py    設置數據存儲模板,用於結構化數據,如:Django的Model

  • pipelines    數據處理行爲,如:通常結構化的數據持久化

  • settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等

  • spiders      爬蟲目錄,如:建立文件,編寫爬蟲規則

注意:通常建立爬蟲文件時,以網站域名命名

  編寫爬蟲

在spiders目錄中新建一系列的定義規則的 xxx.py 文件(文件名本身寫);

示例代碼:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import scrapy

class XiaoHuarSpider(scrapy.spiders.Spider):
    name = "s1"
    # allowed_domains = ["xiaohuar.com"]
    start_urls = [
        "http://www.xiaohuar.com/hua/",
    ]

    def parse(self, response):
        # print(response, type(response))
        # from scrapy.http.response.html import HtmlResponse
        # print(response.body_as_unicode())

        current_url = response.url  # 爬取時請求的url
        body = response.body  # 返回的html
        unicode_body = response.body_as_unicode()  # 返回的html unicode編碼
        print body

注意:

  • 1.爬蟲文件須要定義一個類,並繼承scrapy.spiders.Spider

  • 2.必須定義name,即爬蟲名,若是沒有name,會報錯。

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

  • 4.定義須要爬取的url,放在列表中,由於能夠爬取多個url,Scrapy源碼是一個For循環,從上到下爬取這些url,使用生成器迭代將url發送給下載器下載url的html

  運行

在PyCharm中有至關方便的地方,很好的解決了咱們多餘的操做。

運行命令:

scrapy crawl s1 --nolog  # s1是項目名,見上面的代碼s1在哪。

格式:scrapy crawl  項目名  --nolog      nolog意思是不顯示日誌

  Scrapy查詢

Scrapy內部支持更簡單的查詢語法,幫助咱們在html中查詢咱們須要的標籤和標籤內容以及標籤屬性。

下面以div標籤爲例:

//div  表示查詢某個標籤的全部div標籤
/div   表示查詢某個標籤的兒子
//div[@class='item_list']   表示找到全部的div下屬性爲class='item_list'的
//div[@class='item_list']/div  表示找到這個div的全部兒子
//div[@class='item_list']//span  表示找在這個div下的子子孫孫中的全部span標籤
//div[@class='item_list']//a/text()  表示找在這個div下的子子孫孫中的全部a標籤並得到全部a標籤的內容
//div[@class='item_list']//img/@src  表示找在這個div下的子子孫孫中的全部img標籤並得到全部img標籤的src屬性

示例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import scrapy
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
import re
import urllib
import os


class XiaoHuarSpider(scrapy.spiders.Spider):
    name = "xiaohuar"
    allowed_domains = ["xiaohuar.com"]
    start_urls = [
        "http://www.xiaohuar.com/list-1-1.html",
    ]

    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):
            items = hxs.select('//div[@class="item_list infinite_scroll"]/div')  # //表示找到全部的div
            for i in range(len(items)):
                # 查詢全部img標籤的src屬性,即獲取校花圖片地址
                srcs = hxs.select('//div[@class="item_list infinite_scroll"]'
                                  '/div[%d]//div[@class="img"]/a/img/@src' % i).extract()  
                # 獲取span的文本內容,即校花姓名
                names = hxs.select('//div[@class="item_list infinite_scroll"]'
                                   '/div[%d]//div[@class="img"]/span/text()' % i).extract() 
                # 獲取a的文本內容,即學校名
                schools = hxs.select('//div[@class="item_list infinite_scroll"]'
                                     '/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()' % i).extract()  
                if srcs and names and schools:  # 拿到第一個學校的校花圖片和名字
                    print names[0], schools[0], srcs[0]
                if srcs:
                    ab_src = "http://www.xiaohuar.com" + srcs[0]  # 拼接絕對路徑;就是要爬的url的地址
                    
                    # 文件名,以本身的名字命名;由於python27默認編碼格式是unicode編碼,所以咱們須要編碼成utf-8
                    file_name = "%s_%s.jpg" % (schools[0].encode('utf-8'), names[0].encode('utf-8'))  
                    file_path = os.path.join("E:\\picture", file_name)  # 存放下載圖片的路徑;E:\\picture是我本地存放路徑
                    urllib.urlretrieve(ab_src, file_path)

  遞歸訪問

以上的爬蟲僅僅是爬去初始頁,而咱們爬蟲是須要源源不斷的執行下去,直到全部的網頁被執行完畢

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import scrapy
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
import re
import urllib
import os


class XiaoHuarSpider(scrapy.spiders.Spider):
    name = "xiaohuar"
    allowed_domains = ["xiaohuar.com"]
    start_urls = [
        "http://www.xiaohuar.com/list-1-1.html",
    ]

    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):
            items = hxs.select('//div[@class="item_list infinite_scroll"]/div')  # //表示找到全部的div
            for i in range(len(items)):
                # 查詢全部img標籤的src屬性,即獲取校花圖片地址
                srcs = hxs.select('//div[@class="item_list infinite_scroll"]'
                                  '/div[%d]//div[@class="img"]/a/img/@src' % i).extract()
                # 獲取span的文本內容,即校花姓名
                names = hxs.select('//div[@class="item_list infinite_scroll"]'
                                   '/div[%d]//div[@class="img"]/span/text()' % i).extract()
                # 獲取a的文本內容,即學校名
                schools = hxs.select('//div[@class="item_list infinite_scroll"]'
                                     '/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()' % i).extract()
                if srcs and names and schools:  # 拿到第一個學校的校花圖片和名字
                    print names[0], schools[0], srcs[0]
                if srcs:
                    ab_src = "http://www.xiaohuar.com" + srcs[0]  # 拼接絕對路徑;就是要爬的url的地址

                    # 文件名,以本身的名字命名;由於python27默認編碼格式是unicode編碼,所以咱們須要編碼成utf-8
                    file_name = "%s_%s.jpg" % (schools[0].encode('utf-8'), names[0].encode('utf-8'))
                    file_path = os.path.join("E:\\picture", file_name)  # 存放下載圖片的路徑
                    urllib.urlretrieve(ab_src, file_path)

        # 獲取全部的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)  # 遞歸的找下去

以上代碼將符合規則的頁面中的圖片保存在指定目錄,而且在HTML源碼中找到全部的其餘 a 標籤的href屬性,從而「遞歸」的執行下去,直到全部的頁面都被訪問過爲止。

以上代碼之因此能夠進行「遞歸」的訪問相關URL,關鍵在於parse方法使用了 yield Request對象。

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

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

  正則選擇

語法規則:Selector(response=response查詢對象).xpath('//li[re:test(@class, "item-\d*")]//@href').extract(),

即根據re正則匹配,test即匹配,屬性名是class,匹配的正則表達式是"item-\d*",而後獲取該標籤的href屬性。

 1 from scrapy.selector import Selector
 2 from scrapy.http import HtmlResponse
 3 html = """<!DOCTYPE html>
 4 <html>
 5 <head lang="en">
 6     <meta charset="UTF-8">
 7     <title></title>
 8 </head>
 9 <body>
10     <li class="item-"><a href="link.html">first item</a></li>
11     <li class="item-0"><a href="link1.html">first item</a></li>
12     <li class="item-1"><a href="link2.html">second item</a></li>
13 </body>
14 </html>
15 """
16 response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8')
17 ret = Selector(response=response).xpath('//li[re:test(@class, "item-\d*")]//@href').extract()
18 print(ret)
正則選擇器
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import scrapy
 5 import hashlib
 6 from tutorial.items import JinLuoSiItem
 7 from scrapy.http import Request
 8 from scrapy.selector import HtmlXPathSelector
 9 
10 
11 class JinLuoSiSpider(scrapy.spiders.Spider):
12     count = 0
13     url_set = set()
14 
15     name = "jluosi"
16     domain = 'http://www.jluosi.com'
17     allowed_domains = ["jluosi.com"]
18 
19     start_urls = [
20         "http://www.jluosi.com:80/ec/goodsDetail.action?jls=QjRDNEIzMzAzOEZFNEE3NQ==",
21     ]
22 
23     def parse(self, response):
24         md5_obj = hashlib.md5()
25         md5_obj.update(response.url)
26         md5_url = md5_obj.hexdigest()
27         if md5_url in JinLuoSiSpider.url_set:
28             pass
29         else:
30             JinLuoSiSpider.url_set.add(md5_url)
31             hxs = HtmlXPathSelector(response)
32             if response.url.startswith('http://www.jluosi.com:80/ec/goodsDetail.action'):
33                 item = JinLuoSiItem()
34                 item['company'] = hxs.select('//div[@class="ShopAddress"]/ul/li[1]/text()').extract()
35                 item['link'] = hxs.select('//div[@class="ShopAddress"]/ul/li[2]/text()').extract()
36                 item['qq'] = hxs.select('//div[@class="ShopAddress"]//a/@href').re('.*uin=(?P<qq>\d*)&')
37                 item['address'] = hxs.select('//div[@class="ShopAddress"]/ul/li[4]/text()').extract()
38 
39                 item['title'] = hxs.select('//h1[@class="goodsDetail_goodsName"]/text()').extract()
40 
41                 item['unit'] = hxs.select('//table[@class="R_WebDetail_content_tab"]//tr[1]//td[3]/text()').extract()
42                 product_list = []
43                 product_tr = hxs.select('//table[@class="R_WebDetail_content_tab"]//tr')
44                 for i in range(2,len(product_tr)):
45                     temp = {
46                         'standard':hxs.select('//table[@class="R_WebDetail_content_tab"]//tr[%d]//td[2]/text()' %i).extract()[0].strip(),
47                         'price':hxs.select('//table[@class="R_WebDetail_content_tab"]//tr[%d]//td[3]/text()' %i).extract()[0].strip(),
48                     }
49                     product_list.append(temp)
50 
51                 item['product_list'] = product_list
52                 yield item
53 
54             current_page_urls = hxs.select('//a/@href').extract()
55             for i in range(len(current_page_urls)):
56                 url = current_page_urls[i]
57                 if url.startswith('http://www.jluosi.com'):
58                     url_ab = url
59                     yield Request(url_ab, callback=self.parse)
選擇器規則--Demo
def parse(self, response):
    from scrapy.http.cookies import CookieJar
    cookieJar = CookieJar()
    cookieJar.extract_cookies(response, response.request)
    print(cookieJar._cookies)
獲取響應--Cookies

更多選擇器規則:http://scrapy-chs.readthedocs.io/zh_CN/latest/topics/selectors.html

 

  格式化處理

上述實例只是簡單的圖片處理,因此在parse方法中直接處理。若是對於想要獲取更多的數據(獲取頁面的價格、商品名稱、QQ等),則能夠利用Scrapy的items將數據格式化,而後統一交由pipelines來處理。

在items.py中建立類:

# -*- coding: utf-8 -*-
 
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
 
import scrapy
 
class JieYiCaiItem(scrapy.Item):
 
    company = scrapy.Field()
    title = scrapy.Field()
    qq = scrapy.Field()
    info = scrapy.Field()
    more = scrapy.Field()

上述定義模板,之後對於從請求的源碼中獲取的數據贊成按照此結構來獲取,因此在spider中須要有一下操做:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import scrapy
 5 import hashlib
 6 from beauty.items import JieYiCaiItem
 7 from scrapy.http import Request
 8 from scrapy.selector import HtmlXPathSelector
 9 from scrapy.spiders import CrawlSpider, Rule
10 from scrapy.linkextractors import LinkExtractor
11 
12 
13 class JieYiCaiSpider(scrapy.spiders.Spider):
14     count = 0
15     url_set = set()
16 
17     name = "jieyicai"
18     domain = 'http://www.jieyicai.com'
19     allowed_domains = ["jieyicai.com"]
20 
21     start_urls = [
22         "http://www.jieyicai.com",
23     ]
24 
25     rules = [
26         #下面是符合規則的網址,可是不抓取內容,只是提取該頁的連接(這裏網址是虛構的,實際使用時請替換)
27         #Rule(SgmlLinkExtractor(allow=(r'http://test_url/test?page_index=\d+'))),
28         #下面是符合規則的網址,提取內容,(這裏網址是虛構的,實際使用時請替換)
29         #Rule(LinkExtractor(allow=(r'http://www.jieyicai.com/Product/Detail.aspx?pid=\d+')), callback="parse"),
30     ]
31 
32     def parse(self, response):
33         md5_obj = hashlib.md5()
34         md5_obj.update(response.url)
35         md5_url = md5_obj.hexdigest()
36         if md5_url in JieYiCaiSpider.url_set:
37             pass
38         else:
39             JieYiCaiSpider.url_set.add(md5_url)
40             
41             hxs = HtmlXPathSelector(response)
42             if response.url.startswith('http://www.jieyicai.com/Product/Detail.aspx'):
43                 item = JieYiCaiItem()
44                 item['company'] = hxs.select('//span[@class="username g-fs-14"]/text()').extract()
45                 item['qq'] = hxs.select('//span[@class="g-left bor1qq"]/a/@href').re('.*uin=(?P<qq>\d*)&')
46                 item['info'] = hxs.select('//div[@class="padd20 bor1 comard"]/text()').extract()
47                 item['more'] = hxs.select('//li[@class="style4"]/a/@href').extract()
48                 item['title'] = hxs.select('//div[@class="g-left prodetail-text"]/h2/text()').extract()
49                 yield item
50 
51             current_page_urls = hxs.select('//a/@href').extract()
52             for i in range(len(current_page_urls)):
53                 url = current_page_urls[i]
54                 if url.startswith('/'):
55                     url_ab = JieYiCaiSpider.domain + url
56                     yield Request(url_ab, callback=self.parse)
spider

此處代碼的關鍵在於:

  • 將獲取的數據封裝在了Item對象中
  • yield Item對象 (一旦parse中執行yield Item對象,則自動將該對象交個pipelines的類來處理)
 1 # -*- coding: utf-8 -*-
 2 
 3 # Define your item pipelines here
 4 #
 5 # Don't forget to add your pipeline to the ITEM_PIPELINES setting
 6 # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
 7 
 8 import json
 9 from twisted.enterprise import adbapi
10 import MySQLdb.cursors
11 import re
12 
13 mobile_re = re.compile(r'(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}')
14 phone_re = re.compile(r'(\d+-\d+|\d+)')
15 
16 class JsonPipeline(object):
17 
18     def __init__(self):
19         self.file = open('/Users/wupeiqi/PycharmProjects/beauty/beauty/jieyicai.json', 'wb')
20 
21 
22     def process_item(self, item, spider):
23         line = "%s  %s\n" % (item['company'][0].encode('utf-8'), item['title'][0].encode('utf-8'))
24         self.file.write(line)
25         return item
26 
27 class DBPipeline(object):
28 
29     def __init__(self):
30         self.db_pool = adbapi.ConnectionPool('MySQLdb',
31                                              db='DbCenter',
32                                              user='root',
33                                              passwd='123',
34                                              cursorclass=MySQLdb.cursors.DictCursor,
35                                              use_unicode=True)
36 
37     def process_item(self, item, spider):
38         query = self.db_pool.runInteraction(self._conditional_insert, item)
39         query.addErrback(self.handle_error)
40         return item
41 
42     def _conditional_insert(self, tx, item):
43         tx.execute("select nid from company where company = %s", (item['company'][0], ))
44         result = tx.fetchone()
45         if result:
46             pass
47         else:
48             phone_obj = phone_re.search(item['info'][0].strip())
49             phone = phone_obj.group() if phone_obj else ' '
50 
51             mobile_obj = mobile_re.search(item['info'][1].strip())
52             mobile = mobile_obj.group() if mobile_obj else ' '
53 
54             values = (
55                 item['company'][0],
56                 item['qq'][0],
57                 phone,
58                 mobile,
59                 item['info'][2].strip(),
60                 item['more'][0])
61             tx.execute("insert into company(company,qq,phone,mobile,address,more) values(%s,%s,%s,%s,%s,%s)", values)
62 
63     def handle_error(self, e):
64         print 'error',e
65 
66 pipelines
pipelines

上述代碼中多個類的目的是,能夠同時保存在文件和數據庫中,保存的優先級能夠在配置文件settings中定義。

ITEM_PIPELINES = {
    'beauty.pipelines.DBPipeline': 300,
    'beauty.pipelines.JsonPipeline': 100,
}
# 每行後面的整型值,肯定了他們運行的順序,item按數字從低到高的順序,經過pipeline,一般將這些數字定義在0-1000範圍內。

  

更多詳見:http://www.cnblogs.com/wupeiqi/articles/5354900.html

相關文章
相關標籤/搜索