srcapy爬蟲框架

 

一.什麼是Srcapy?

  Srcapy是爲了爬取網站數據,提取結構性數據而編寫的應用框架,很是出名,很是強悍.他就是一個已經被集成各類功能包括高性能異步下載,隊列,分佈式,解析,持久化等的強大通用性項目模板(超級武器霸王).主要學習它的特性,各個功能用法.html

二.安裝

  Linux:pip3 install scrapypython

  Windows:web

      1.pip3 install wheelredis

      2.下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twistedchrome

      3.進入下載目錄,執行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl數據庫

      4.pip3 install pywin32json

      5.pip3 install scrapy瀏覽器

三.基礎使用

  1.建立項目:scrapy startproject +項目名稱cookie

    項目結構:網絡

project_name/
   scrapy.cfg:
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py

scrapy.cfg   項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
items.py    # 設置數據存儲模板,用於結構化數據,如:Django的Model
pipelines   # 數據持久化處理
settings.py # 配置文件,如:遞歸的層數、併發數,延遲下載等
spiders     # 爬蟲目錄,如:建立文件,編寫爬蟲解析規則

  2.建立爬蟲應用程序:

cd project_name  #進入項目目錄 
scrapy genspider # 應用名稱 爬取網頁的起始url  (例如:scrapy genspider qiubai www.qiushibaike.com)

  3.編寫爬蟲文件:在步驟2執行完畢後,會在項目的spiders中生成一個應用名的爬蟲文件

import scrapy

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai' #應用名稱
    #容許爬取的域名(若是遇到非該域名的url則爬取不到數據)
    allowed_domains = ['https://www.qiushibaike.com/']
    #起始爬取的url
    start_urls = ['https://www.qiushibaike.com/']

     #訪問起始URL並獲取結果後的回調函數,該函數的response參數就是向起始的url發送請求後,獲取的響應對象.該函數返回值必須爲可迭代對象或者NUll 
     def parse(self, response):
            pass

  4.設置修改settings.py配置文件配置

    添加本身電腦的用戶代理信息;

    將遵照robts協議改成False.

  5.執行爬蟲程序:scrapy crawl 應用名稱,該種執行形式會顯示執行的日誌信息

         scrapy crawl 應用名稱 --nolog:執行時不返回日誌信息 

  

  愛之初體驗:

# -*- coding: utf-8 -*-
import scrapy


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
   # allowed_domains = ['www.qiushibaike.com']
    start_urls = ['http://www.qiushibaike.com/']

    def parse(self, response):
        info_list = []
        li_list = response.xpath('//*[@class="recmd-right"]')
        for li in li_list:
            content = li.xpath('./a/text()')[0].extract()
            author = li.xpath('./div/a/span/text()').extract_first()
            info_dic = {'content':content,
                        'author':author}
            print(info_dic)
            info_list.append(info_dic)
        return info_list

  

 四.scrapy的持久化存儲

  1.基於終端指令的持久化存儲

    保證爬蟲文件的parse方法中有可迭代對象列表或字典的返回,該返回值能夠經過終端指令的形式指定格式的文件中進行持久化操做.可是隻能夠存儲一下格式文件:

     

    執行輸出指定格式進行存儲

    scrapy crawl 爬蟲名稱 -o xxx.json
    scrapy crawl 爬蟲名稱 -o xxx.xml
    scrapy crawl 爬蟲名稱 -o xxx.csv

  2.基於管道的持久化存儲

    scrapy框架中已經封裝好了高效便捷的持久化操做功能,咱們直接使用便可.使用前先了解如下文件:items.py: 數據結構模板文件,定義數據屬性

pipelines.py: 管道文件,接受數據(items),進行持久化操做

持久化流程:
1.數據解析 2.爬蟲文件爬去到數據後,須要將數據封裝到items對象中. 3.使用yield關鍵字將items對象提交給pipelines管道進行持久化. 4.在管道文件中的process_item方法中接受爬蟲文件提交過來的items對象,而後編寫持久化存儲的代碼將item對象中存儲的數據進行持久化存儲. 5.settings.py配置文件中開啓管道

    愛之初體驗:持久化存儲


爬蟲文件:qiubai.py

# -*- coding: utf-8 -*-
import scrapy
from papa.items import PapaItem

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
   # allowed_domains = ['www.qiushibaike.com']
    start_urls = ['http://www.qiushibaike.com/']

    def parse(self, response):
        li_list = response.xpath('//*[@class="recmd-right"]')
        for li in li_list:
            content = li.xpath('./a/text()')[0].extract()
            author = li.xpath('./div/a/span/text()').extract_first()

            item = PapaItem()
            item['content'] = content
            item['author'] = author

            yield item

 items文件:items.py

import scrapy


class PapaItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()

    author = scrapy.Field()
    content = scrapy.Field()

 管道文件:pipelines.py

class PapaPipeline(object):

    #構造方法
    def __init__(self):
        self.f = None   #定義一個文件描述屬性

    #下列都是在重寫父類的方法:
    #開始爬蟲時,執行一次
    def open_spider(self,spider):
        print('開始爬')
        self.f = open('./data.txt','w') #由於該方法會被執行調用不少次,因此文件的開啓和關閉操做寫在了另外兩個各自執行一次的方法中

    def process_item(self, item, spider):
        #將爬蟲程序提交的item進行持久化存儲
        self.f.write(item['author'] + ':' + item['content'] + '\n')
        return item

    #結束爬蟲時,關閉一次文件
    def close_spider(self,spider):
        self.f.close()
        print('結束爬')

  配置文件:settings.py

ITEM_PIPELINES = {
    'papa.pipelines.PapaPipeline': 300, #300表示爲優先級,值越小優先級越高
}

  爲愛鼓掌:基於redis的管道存儲,在上述案例中,在管道文件裏將item對象中的數據存儲到磁盤中,若是將上述案例中的item數據寫入redis數據庫的話,只須要將上述的管道文件修改爲以下形式:

pipelines.py文件

from redis import Redis
class PapaPipeline(object):
    conn = None

    def open_spider(self,spider):
        print('開始爬')
        #建立鏈接對象
        self.conn = Redis(host='127.0.0.1',port=6379)

    def process_item(self, item, spider):
        #將爬蟲程序提交的item進行持久化存儲
        dict = {
            'author':item['author'],
            'content':item['content']
        }
        self.conn.lpush('data',dict)
        return item

五.scrapy的post請求

  默認狀況下,scrapy採用的是get請求,若是想發起post請求,則須要重寫start_requests方法,使其發起post請求.

import scrapy


class BbbSpider(scrapy.Spider):
    name = 'bbb'
    # allowed_domains = ['www.baidu.com']
    start_urls = ['https://baike.baidu.com/sug/']

    def start_requests(self):
        #post請求參數
        data = {
            'kw':'dog',
        }

        for url in self.start_urls:
            yield  scrapy.FormRequest(url=url,formdata=data,callback=self.parse)

    def parse(self, response):
       print(response.text)

  

六.scrapy的請求傳參

  在平時的狀況下,咱們只爬取特定的一個頁面並不能知足咱們的需求,好比在電影網站中不只要看簡介,還要爬取其二級網頁中的詳情信息,這時咱們就須要用到傳參.

  spider文件

# -*- coding: utf-8 -*-
import scrapy
from zhaogongzuo.items import ZhaogongzuoItem

class A51jobSpider(scrapy.Spider):
    name = '51job'
    #allowed_domains = ['www.baidu.com']
    start_urls = ['https://search.51job.com/list/010000,000000,0000,00,9,99,%25E7%2588%25AC%25E8%2599%25AB,2,1.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=4&dibiaoid=0&address=&line=&specialarea=00&from=&welfare=']

    def parse(self, response):
       div_list = response.xpath('//div[@class="el"]')
       for div in div_list:
           item = ZhaogongzuoItem()
           item['job'] = div.xpath('./p/span/a/text()').extract_first()

           item['detail_url'] = div.xpath('./p/span/a/@href').extract_first()

           item['company'] = div.xpath('./span[@class="t2"]/a/text()').extract_first()

           item['salary'] = div.xpath('./span[@class="t4"]/text()').extract_first()

           print(item)
            #meta參數:請求參數.meta字典就會傳遞給回調函數的response
           yield scrapy.Request(url=item['detail_url'], callback=self.parse_detail, meta={'item': item})

    def parse_detail(self,response):
       #response.meta 返回接受到的meta字典
       item = response.meta['item']
       item['detail'] = response.xpath('/html/body/div[3]/div[2]/div[3]/div[1]/div/text()').extract_first()
       print(123)
       yield item

    items文件

import scrapy


class ZhaogongzuoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    job = scrapy.Field()
    company = scrapy.Field()
    salary = scrapy.Field()
    detail = scrapy.Field()
    detail_url = scrapy.Field()

    pipielines文件

import json

class ZhaogongzuoPipeline(object):
    def __init__(self):
        self.f = open('data.txt','w')
    def process_item(self, item, spider):
        dic = dict(item)
        print(dic)
        json.dump(dic,self.f,ensure_ascii=False)
        return item
    def close_spider(self,spider):
        self.f.close()

  settings文件更改的仍是那些......

七.五大核心組件工做流程

  

引擎(Scrapy):用來處理整個系統的數據流處理,出發事務(框架核心)

調度器(Scheduler):用來接受引擎發過來的請求,壓入隊列中,並在引擎再次請求的時候返回,能夠想象成一個url的優先隊列,由它來決定下一個要抓取的網址是什麼,同時去除重複的網址.

下載器(Downloader):是全部組件中負擔最大的,它用於高速地下載網絡上的資源。Scrapy的下載器代碼不會太複雜,但效率高,主要的緣由是Scrapy下載器是創建在twisted這個高效的異步模型上的(其實整個框架都在創建在這個模型上的)。

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

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

 八.scrapy的日誌等級

  日誌:就是在終端運行項目時,在小黑框中打印輸出信息.

  日誌信息的種類:

        WARNING:警告;

        INFO:通常的信息;

        ERROR:通常錯誤;

        DEBUG:調式信息

  設置使用日誌信息:

    settings.py配置文件中加入:LOG_LEVEL= '指定信息種類'

LOG_FILE = 'log.txt'  #表示將日誌信息寫入log.txt文件中

九.scrapy中selenium的使用

  在使用scrapy框架爬取某些網站時,會遇見數據懶加載的的狀況.結合selenium建立瀏覽器對象,經過此瀏覽器發送請求會迎刃而解.

  原理:當引擎將網站url對應的請求提交給下載器後,下載器進行網頁數據的下載,而後將下載到的頁面數據封裝到response,提交給引擎,引擎將response再轉交給spiders.spiders接受到的response對象中存儲的頁面數據是沒有動態加載的數據的.要想獲取動態數據,則須要在下載中間件對下載器提交給引擎的response響應對象進行攔截,且對其內部存儲的頁面數據進行篡改,修改爲攜帶了動態加載出的新聞數據,燃火將被篡改的response對象最終交給spiders進行解析操做.

  使用流程:

      重寫爬蟲文件的構造方法,在該方法中使用selenium實例化一個瀏覽器對象(瀏覽器對象只需實例化一次);

      重寫爬蟲文件的closed(self,spider)方法,在其內部關閉瀏覽器對象.該方法是在爬蟲結束時被調用;

      在下載中間件類的process_response方法中接收spider中的瀏覽器對象;

      處理執行相關自動化操做(發起請求,獲取頁面數據);

      實例化一個新的響應對象(from scrapy.http import HtmlResponse),且將頁面數據存儲到該對象中;

      返回新的響應對象;

      在配置文件中開啓下載中間件.

   spider文件

import scrapy
from selenium import webdriver


class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://news.163.com/air/']
    def __init__(self):
        self.drv = webdriver.Chrome(executable_path=r'C:\Users\Air\Anaconda3\爬蟲\chromedriver.exe')

    def parse(self, response):
        div_list = response.xpath('//div[@class="data_row news_article clearfix "]')
        for div in div_list:
            title = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
            print(title)

    def closed(self,spider):
        print('關閉瀏覽器對象!')
        self.drv.quit()

    middlewas文件

from scrapy.http import HtmlResponse
from time import sleep
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        print('即將返回一個新的響應對象')
        drv = spider.drv
        drv.get(url=request.url)
        sleep(3)
        #包含了動態加載出來的新聞數據
        page_text = drv.page_source
        sleep(3)

        return HtmlResponse(url=spider.drv.current_url,body=page_text,encoding='utf-8',request=request)

十.如何增長爬蟲效率

增長併發:默認scrapy開啓的併發線程爲32個,能夠適當進行增長.在settings配置文件中修改CONCURRENT_REQUESTS = 100,這樣就設置成100啦.
下降日誌級別:在運行scrapy時,會有大量日誌信息的輸出,爲了減小cpu的使用頻率,能夠設置log輸出信息爲INFO或者ERROR便可.在配置文件中編寫:LOG_LEVEL= 'INFO'.
禁止cookie:若是不是真的須要cookie,則在scrapy爬取數據時能夠禁止cookie從而減小cpu的使用率,提高爬取效率.在配置文件中編寫:COOKIES_ENABLED = False.
禁止重試:對失敗的HTTP進行重試新請求(重試)會減慢爬取速度,所以能夠禁止重試.在settings文件中編寫:RETRY_ENABLED = False.
減小下載超時:若是對一個很是慢的連接進行爬取,減小下載超時能夠能讓卡主的連接快速被放棄,從而提高效率.在配置文件中編寫:DOWNLOAD_TIMEOUT = 10,超過期間爲10s.

十一.分佈式爬蟲

  原生的的scrapy框架不能夠實現分佈式:由於調度器不能被共享;管道沒法被共享

  使用到的scrapy-redis組件做用:提供了能夠被共享的調度器和管道.

分佈式爬蟲實現流程:

1.環境安裝:pip install scrapy-redis
2.建立工程
3.建立爬蟲文件:RedisCrawlSpider RedisSpider
        -scrapy genspider -t crawl xxx www.xxx.com
4.對爬蟲文件中的相關屬性進行修改:
    導包:from scrapy_redis.spiders import RedisCrawlSpider
    將當前爬蟲文件的父類設置成RedisCrawlSpider
    將起始url列表替換成redis_key = 'xxx' (調度器隊列的名稱)
5.在配置文件中進行配置
    使用組件中封裝好的能夠被共享的管道類:
    ITEM_PIPELINES = {
                'scrapy_redis.pipelines.RedisPipeline':400
            }  
    配置調度器(使用組件中封裝好的能夠被共享的調度器):
    #增長了一個去重容器類的配置,做用使用Redis的set集合來儲存請求的指紋數據,從而實現請求去重的持久化.
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFDupeFilter"
    #使用scrapy-redis組件本身的調度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    #配置調度器是否要持久化,也就是當爬蟲結束了要不要清空redis中請求列隊和去重指紋的set.若是是True,就表示須要持久化存儲,就不清空數據,不然清空數據
    SCHEDULER_PERSITE= True
    #指定存儲數據的redis
    REDIS_HOST = 'redis服務的ip地址'
    REDIS_POST = 6379
    #配置redis數據庫的配置文件
       -取消保護模式:protected-mode no
       -bind 綁定:#bind 127.0.0.1
    -啓動redis
6.執行分佈式程序
  scrapy runspider xxx.py
7.向調度器隊列中寫入一個起始url:
  在redis-cli中執行

十二.增量式爬蟲

  什麼是增量式爬蟲:經過爬蟲程序檢測某網站數據更新的狀況,以變爬到該網站更新出的新數據.

  如何進行:(1) 在發送請求以前判斷這個url是否是以前爬過

       (2) 在解析內容後判斷這部份內容是否是以前爬過.

       (3) 寫入存儲介質時判斷內容是否是已經在介質中存在.

          分析:能夠發現,其實增量爬取的核心就是去重,至於去重的操做在哪步起做用,只能說各有利弊.建議(1)(2)思路根據實際狀況取一個(也可能都用).(1)思路適合不斷有新頁面出現的網站,好比說小說的新章節,天天的最新新聞等等;(2)思路則適合頁面內容會更新的網站;(3)思路至關因而最後一道防線,這樣作能夠最大程度上達到去重的目的.

  去重方法:

      將爬取過程當中產生的url進行存儲,存儲在redis中的set中.當下次進行數據爬取時,首先對即將發起的請求對應url在存儲的url的set中作判斷,若是存在則不進行請求,不然才請求;

      對爬取到的網頁內容進行惟一標識的制定,而後將該惟一標識存儲至redis的set中,當下次爬到網頁數據的時候,在進行持久化存儲以前,首先能夠先判斷該數據的惟一標識在redis的set中是否存在,在決定是否進行持久化存儲.

爬蟲文件:

from redis import Redis
class IncrementproPipeline(object):
    conn = None
    def open_spider(self,spider):
        self.conn = Redis(host='127.0.0.1',port=6379)
    def process_item(self, item, spider):
        dic = {
            'name':item['name'],
            'kind':item['kind']
        }
        print(dic)
        self.conn.lpush('movieData',dic)
        return item
- 需求:爬取糗事百科中的段子和做者數據。

爬蟲文件:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from incrementByDataPro.items import IncrementbydataproItem
from redis import Redis
import hashlib
class QiubaiSpider(CrawlSpider):
    name = 'qiubai'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    rules = (
        Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True),
        Rule(LinkExtractor(allow=r'/text/$'), callback='parse_item', follow=True),
    )
    #建立redis連接對象
    conn = Redis(host='127.0.0.1',port=6379)
    def parse_item(self, response):
        div_list = response.xpath('//div[@id="content-left"]/div')

        for div in div_list:
            item = IncrementbydataproItem()
            item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first()

            #將解析到的數據值生成一個惟一的標識進行redis存儲
            source = item['author']+item['content']
            source_id = hashlib.sha256(source.encode()).hexdigest()
            #將解析內容的惟一表示存儲到redis的data_id中
            ex = self.conn.sadd('data_id',source_id)

            if ex == 1:
                print('該條數據沒有爬取過,能夠爬取......')
                yield item
            else:
                print('該條數據已經爬取過了,不須要再次爬取了!!!')

  管道文件:

from redis import Redis
class IncrementbydataproPipeline(object):
    conn = None

    def open_spider(self, spider):
        self.conn = Redis(host='127.0.0.1', port=6379)

    def process_item(self, item, spider):
        dic = {
            'author': item['author'],
            'content': item['content']
        }
        # print(dic)
        self.conn.lpush('qiubaiData', dic)
        return item
相關文章
相關標籤/搜索