爬蟲之scrapy框架

1.配置環境:

1.介紹:是一個具備不少功能且具備很強通用性的一個項目模板
2.Linux: 直接 pip install scrapy
3.windows:
    1.pip install wheel
    2.下載twisted https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    3.進入到下載的目錄 pip install Twisted-19.2.0-cp36-cp36m-win_amd64
    4.pip install pywin32
    5.pip install scrapy

2.命令:

1.建立項目: scrapy startproject 項目名稱
2.建立爬蟲的應用程序;
    進入項目的目錄
    scrapy genspider 應用的名稱 爬取網頁的其實url 
3.編寫爬蟲文件,在第二步執行完的時候就會在項目的spiders中生成一個應用名的py爬蟲文件
4.修改settings.py配置文件的相關配置
5.執行爬蟲程序在cmd中
	scrapy crawl 應用的名稱

3.基礎使用:

1.建立項目: scrapy startproject 項目名稱
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.建立爬蟲的應用程序;
    進入項目的目錄
    scrapy genspider 應用的名稱 爬取網頁的其實url 

  

3.編寫爬蟲文件,在第二步執行完的時候就會在項目的spiders中生成一個應用名的py爬蟲文件

# -*- coding: utf-8 -*-
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):
        print(response.text) #獲取字符串類型的響應內容
        print(response.body)#獲取字節類型的相應內容

  

4.修改settings.py配置文件的相關配置
19行:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #假裝請求載體身份

22行:ROBOTSTXT_OBEY = False  #能夠忽略或者不遵照robots協議

  

5.執行爬蟲程序在cmd中
scrapy crawl 應用的名稱

4.案例

例子: 爬取站長素材的圖片 在scrapy項目和爬蟲應用建好的狀況下:
from urllib import request

import requests
import scrapy
import os
from lxml import etree


class ZzmeiziSpider(scrapy.Spider):
    name = 'zzmeizi'
    # allowed_domains = ['http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html']
    start_urls = ['http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html']

    def parse(self, response):
        # print(response.text)  # 獲取字符串類型的響應內容
        # print(response.body)  # 獲取字節類型的相應內容
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
        }
        tree = etree.HTML(response.body)
        ret_list = tree.xpath('//*[@id="container"]//div/div/a/@href')
        print(ret_list)
        if not os.path.exists('./zzmeizi'):
            os.mkdir('./zzmeizi')

        for div in ret_list:
            page_text = requests.get(url=div, headers=headers).content
            tree = etree.HTML(page_text)
            img_name = tree.xpath('//div[@class="imga"]/a/@title')[0]
            img_url = tree.xpath('//div[@class="imga"]/a/@href')[0]
            request.urlretrieve(img_url, './zzmeizi/%s.jpg' % (img_name)) // 訪問並保存 

5.使用管道進行持久化存儲:

全站數據的爬取:手動請求的發送
scrapy默承認以進行cookie的攜帶和處理

處理請求傳參:
    - 使用場景:爬取解析的數據沒有存在於同一張頁面中
    - meta參數

持久化存儲的實現流程(基於管道):
    - 數據解析
    - 封裝item類(將字段先在items文件中封裝一下)
    - 實例化item類型的對象
    - 將解析到的數據依次存儲封裝到item類型的對象中
    - 將item提交給管道
    - 在管道中實現io操做
    - 在settings文件中開啓管道
    將一份數據存儲到不一樣的平臺中
    	- 管道文件中一個管道類負責將item存儲到某一個平臺中
    	- 配置文件中設定管道類的優先級
    	- process_item 方法中return item 的操做將item傳遞給下一個即將被執行的管道類
import scrapy
class XioahuaproItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    img_url = scrapy.Field()
封裝item
class MysqlPipeline(object):
    conn = None
    cursor = None
    def open_spider(self, spider):
        #解決數據庫字段沒法存儲中文處理:alter table tableName convert to charset utf8;
        self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='test')
        print(self.conn)
    def process_item(self, item, spider):
        self.cursor = self.conn.cursor()
        try:
            self.cursor.execute('insert into xiahua values ("%s","%s")'%(item['name'],item['img_url']))
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()
        return item
    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()
將數據持久化到數據庫
import scrapy
class PostdemoSpider(scrapy.Spider):
    name = 'postDemo'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://fanyi.baidu.com/sug']

    def start_requests(self):
        data = {
            'kw':'dog'
        }
        for url in self.start_urls:
            yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)

    def parse(self, response):
        print(response.text)
使用scrapy框架完成post請求
當進行深層爬取時須要進行數據的傳遞;
class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/9.html']
    #接收一個請求傳遞過來的數據
    def detail_parse(self,response):
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc

        yield item
    def parse(self, response):
        li_list = response.xpath('//div[@class="stui-pannel_bd"]/ul/li')
        for li in li_list:
            name = li.xpath('.//h4[@class="title text-overflow"]/a/text()').extract_first()
            detail_url = 'https://www.4567tv.tv'+li.xpath('.//h4[@class="title text-overflow"]/a/@href').extract_first()
            item = MovieproItem()
            item['name'] = name
            #meta是一個字典,字典中全部的鍵值對均可以傳遞給指定好的回調函數
            yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
meta傳遞參數

6.增長併發

增長併發:
默認scrapy開啓的併發線程爲32個,能夠適當進行增長。在settings配置文件中修改CONCURRENT_REQUESTS = 100值爲100,併發設置成了爲100。

下降日誌級別:
    在運行scrapy時,會有大量日誌信息的輸出,爲了減小CPU的使用率。能夠設置log輸出信息爲INFO或者ERROR便可。在配置文件中編寫:LOG_LEVEL = ‘INFO’

禁止cookie:
    若是不是真的須要cookie,則在scrapy爬取數據時能夠禁止cookie從而減小CPU的使用率,提高爬取效率。在配置文件中編寫:COOKIES_ENABLED = False

禁止重試:
    對失敗的HTTP進行從新請求(重試)會減慢爬取速度,所以能夠禁止重試。在配置文件中編寫:RETRY_ENABLED = False

減小下載超時:
    若是對一個很是慢的連接進行爬取,減小下載超時能夠能讓卡住的連接快速被放棄,從而提高效率。在配置文件中進行編寫:DOWNLOAD_TIMEOUT = 10 超時時間爲10s

  

7.中間件

下載中間件

​	- 做用:批量攔截請求個響應

​	- 攔截請求:

​		1. 篡改請求頭信息(User-Agent):

​		2.處理異常的請求

​		3.對響應數據進行修改

1.在scrapy中如何給全部的請求對象儘量多的設置不同的請求載體身份表示

- UA池, process_request(request) 中間件中處理

2.scrapy 中如何給發生異常請求設置代理IP

- ip池, process_exception(request,response,spider):request.meta['proxy' ] = 'http://ip:prot'
- 講異常的請求攔截到以後,經過代理ip相關的操做,就能夠將異常的請求變爲非異常的請求,對該請求從新發送: return request

3.簡述下載中間件的做用及其最重要三個方法的做用
- process_request:攔截全部非異常的請求
- process_response:攔截全部的響應對象
- process_exception:攔截髮生異常的請求對象,須要對異常的請求對象進行相關處理,讓其變成
    正常的請求對象,而後經過return request 對該請求進行從新發送

 4.簡述scrapy中何時須要使用selenium及其scrapy應用selenium的編碼流程php

- 實例化瀏覽器對象(一次):spider的init方法
- 須要編寫瀏覽器自動化的操做(中間件的process_response)
- 關閉瀏覽器(spider的closed方法中進行關閉操做)

 

8.crawlSpider

- crawlSpider全站數據爬取
    - crawlSpider就是spider一個子類(派生)
    - crawlSpider具備的機制:
        - 鏈接提取器
        - 規則解析器
    - 建立爬蟲文件:
    - 深度爬取
    - 命令
    	- scrapy genspider -t crawl 應用名 url地址

  

# 爬蟲文件 
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from bossPro.items import DetailItem,FirstItem
#爬取的是崗位名稱(首頁)和崗位描述(詳情頁)
class BossSpider(CrawlSpider):
    name = 'boss'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.zhipin.com/c101010100/?query=python%E5%BC%80%E5%8F%91&page=1&ka=page-prev']
    #獲取全部的頁碼鏈接
    link = LinkExtractor(allow=r'page=\d+')
    link_detail = LinkExtractor(allow=r'/job_detail/.*?html')
    #/job_detail/f2a47b2f40c53bd41XJ93Nm_GVQ~.html
    #/job_detail/47dc9803e93701581XN80ty7GFI~.html
    rules = (
        Rule(link, callback='parse_item', follow=True),
        Rule(link_detail, callback='parse_detail'),
    )
    #將頁碼鏈接對應的頁面數據中的崗位名稱進行解析
    def parse_item(self, response):
        li_list = response.xpath('//div[@class="job-list"]/ul/li')
        for li in li_list:
            item = FirstItem()
            job_title = li.xpath('.//div[@class="job-title"]/text()').extract_first()
            item['job_title'] = job_title
            # print(job_title)

            yield item
    def parse_detail(self,response):
        job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
        item = DetailItem()
        job_desc = ''.join(job_desc)
        item['job_desc'] = job_desc

        yield item
案例 爬取BOSS直聘 爬蟲文件
# item 文件
import scrapy
class DetailItem(scrapy.Item):
    # define the fields for your item here like:
    job_desc = scrapy.Field()
class FirstItem(scrapy.Item):
    # define the fields for your item here like:
    job_title = scrapy.Field()
item文件
# 管道 持久化文件
class BossproPipeline(object):
    f1,f2 = None,None
    def open_spider(self,spider):
        self.f1 = open('a.txt','w',encoding='utf-8')
        self.f2 = open('b.txt', 'w', encoding='utf-8')
    def process_item(self, item, spider):

        #item在同一時刻只能夠接收到某一個指定item對象
        if item.__class__.__name__ == 'FirstItem':
            job_title = item['job_title']
            self.f1.write(job_title+'\n')
        else:
            job_desc = item['job_desc']
            self.f2.write(job_desc)
        return item
# settings 文件 要加UA 而且把ROBOTSTXT_OBEY 改成False
持久化文件

9.分佈式爬蟲

概念:使用多臺機器組成一個分佈式的機羣,在機羣中運行同一組程序,進行聯合數據的爬取。
原生的scrapy是不能夠實現分佈式:
        - 原生的scrapy中的調度器不能夠被共享
        - 原生的scrapy的管道不能夠被共享
    - 若是實現分佈式就必須使用scrapy-redis(模塊)
        - 能夠給原生的scrapy提供能夠被共享的管道和調度器
        - pip install scrapy_redis
搭建流程:
        - 建立工程 
        	- scrapy startproject 項目名稱
        - 爬蟲文件
        	- scrapy genspider -t crawl 應用名 url地址
        - 修改爬蟲文件:
            - 導報:from scrapy_redis.spiders import RedisCrawlSpider
            - 將當前爬蟲類的父類進行修改RedisCrawlSpider
            - allowed_domains,start_url刪除,添加一個新屬性redis_key(調度器隊列的名稱)
            - 數據解析,將解析的數據封裝到item中而後向管道提交
        - 配置文件的編寫:
            - 指定管道:
                                ITEM_PIPELINES = {
                         'scrapy_redis.pipelines.RedisPipeline': 400
                        }
            - 指定調度器:
                # 增長了一個去重容器類的配置, 做用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重的持久化
                DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
                # 使用scrapy-redis組件本身的調度器
                SCHEDULER = "scrapy_redis.scheduler.Scheduler"
                # 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。若是是True, 就表示要持久化存儲, 就不清空數據, 不然清空數據
                SCHEDULER_PERSIST = True
            - 指定具體的redis:
                REDIS_HOST = 'redis服務的ip地址'
                REDIS_PORT = 6379
                REDIS_ENCODING = ‘utf-8’
                REDIS_PARAMS = {‘password’:’123456’}
            - 開啓redis服務(攜帶redis的配置文件:redis-server ./redis.windows.conf),和客戶端:
                - 對redis的配置文件進行適當的配置:
                        - #bind 127.0.0.1
                        - protected-mode no
                 - 開啓
             - 啓動程序:scrapy runspider xxx.py
             - 向調度器隊列中扔入一個起始的url(redis的客戶端):lpush xxx www.xxx.com
                - xxx表示的就是redis_key的屬性值
- 問題:
	- 可能會出現,只要一臺電腦在工做,而其餘電腦沒有工做,把setting文件裏的
	CONCURRENT_REQUESTS = 32 改的小一點

  

# 爬蟲文件
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fbsPro.items import FbsproItem
class TestSpider(RedisCrawlSpider):
    name = 'test'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    #調度器隊列的名稱
    redis_key = 'dongguan'
    rules = (
        Rule(LinkExtractor(allow=r'type=4&page=\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        a_list = response.xpath('//a[@class="news14"]')
        for a in a_list:
            item = FbsproItem()
            item['title']= a.xpath('./text()').extract_first()

            yield item
案例 爬蟲文件
# item 文件
import scrapy
class FbsproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
item文件
#settings 文件 添加一下配置
ITEM_PIPELINES = {
   # 'fbsPro.pipelines.FbsproPipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400 # 數字表示權重
}
# 增長了一個去重容器類的配置, 做用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis組件本身的調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。若是是True, 就表示要持久化存儲, 就不清空數據, 不然清空數據
SCHEDULER_PERSIST = True

REDIS_HOST = '***.***.**.***' # 鏈接redis 數據庫的地址
REDIS_PORT = 6379   #鏈接數據庫的端口
Settings文件
# 管道文件
因爲使用了 分佈式爬蟲,數據存儲在redis數據庫中,若是要作持久化存儲的話就必須將數據從redis數據庫中讀取出來再進行存儲
管道文件

10.增量式爬蟲

- 概念:用來《檢測》網站數據更新的狀況。只會爬取網站中更新出來的新數據。
- 核心:去重
# 爬蟲文件
import scrapy
from qiubaiPro.items import QiubaiproItem
import hashlib
from redis import Redis
class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    conn = Redis(host='127.0.0.1',port=6379)
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//div[@id="content-left"]/div')
        for div in div_list:
            #數據指紋:爬取到一條數據的惟一標識
            author = div.xpath('./div/a[2]/h2/text() | ./div/span[2]/h2/text()').extract_first()
            content = div.xpath('./a/div/span//text()').extract()
            content = ''.join(content)

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

            #數據指紋的建立
            data = author+content
            hash_key = hashlib.sha256(data.encode()).hexdigest()
            ex = self.conn.sadd('hash_keys',hash_key)
            if ex == 1:
                print('有新數據更新......')
                yield item
            else:
                print('無數據更新!')
案例 爬蟲文件
#  items 文件
import scrapy
class QiubaiproItem(scrapy.Item):
    # define the fields for your item here like:
    author = scrapy.Field()
    content = scrapy.Field()
item文件
# settings 文件 
配置好 UA 而且把ROBOTSTXT_OBEY 改成False
Settings文件
相關文章
相關標籤/搜索