1. 程式人生 > 實用技巧 >scrapy知識進階

scrapy知識進階

scrapy得請求傳參

# 把藥傳遞的資料放到meta中
yield Request(url=next_url,meta={'item':item},callback=self.parse)
# 在response物件中取出來
item=response.meta.get('item')

download元件會將當次請求(request)所攜帶的meta賦值給當次響應(response)的meta

如何提高scrapy的爬蟲效率

- 在配置檔案中進行相關的配置即可:(預設還有一套setting)
#1 增加併發:
預設scrapy開啟的併發執行緒為32個,可以適當進行增加。在settings配置檔案中修改CONCURRENT_REQUESTS = 100值為100,併發設定成了為100。
#2 降低日誌級別:
在執行scrapy時,會有大量日誌資訊的輸出,為了減少CPU的使用率。可以設定log輸出資訊為INFO或者ERROR即可。在配置檔案中編寫:LOG_LEVEL = ‘INFO’
# 3 禁止cookie:
如果不是真的需要cookie,則在scrapy爬取資料時可以禁止cookie從而減少CPU的使用率,提升爬取效率。在配置檔案中編寫:COOKIES_ENABLED = False
# 4禁止重試:
對失敗的HTTP進行重新請求(重試)會減慢爬取速度,因此可以禁止重試。在配置檔案中編寫:RETRY_ENABLED = False
# 5 減少下載超時:
如果對一個非常慢的連結進行爬取,減少下載超時可以能讓卡住的連結快速被放棄,從而提升效率。在配置檔案中進行編寫:DOWNLOAD_TIMEOUT = 10 超時時間為10s

<1>增加併發數

在scrapy開啟的預設併發執行緒為32個,可以適當地增加。
在settings配置檔案中修改:
CONCURRENT_REQUESTS = 100

<2>降低日誌級別

在執行scrapy時,會有大量日誌資訊的輸出,為了減少CPU的使用率。
可以設定log輸出資訊為INFO或者ERROR即可。
在配置檔案中編寫:LOG_LEVEL = ‘INFO’

<3>禁止cookie

如果不是真的需要cookie,則在scrapy爬取資料時可以禁止cookie從而減少CPU的使用率,提升爬取效率。
在配置檔案中編寫:COOKIES_ENABLED = False

<4>禁止重試

對失敗的HTTP進行重新請求(重試)會減慢爬取速度,因此可以禁止重試。
在配置檔案中編寫:RETRY_ENABLED = False

<5>減少下載超時

如果對一個非常慢的連結進行爬取,減少下載超時可以能讓卡住的連結快速被放棄,從而提升效率。
在配置檔案中進行編寫:DOWNLOAD_TIMEOUT = 10 超時時間為10s

實現流程:

scrapy有自己一套預設的內建配置檔案,你在settings.py中寫的配置,會被覆蓋預設的配置選項,先將預設的配置載入到字典中,在將你設定的配置update 進去。

scrapy的中介軟體

scrapy的中介軟體分為:爬蟲中介軟體和下載中介軟體,都寫在middlewares中

# 配置檔案
DOWNLOADER_MIDDLEWARES = {
   'firstscrapy.middlewares.FirstscrapyDownloaderMiddleware': 543,
}

SPIDER_MIDDLEWARES = {
    'firstscrapy.middlewares.FirstscrapySpiderMiddleware': 543,
}

下載中介軟體

def process_request(self, request, spider):
    # Called for each request that goes through the downloader
    # middleware.

    # Must either:
    # - return None: continue processing this request 
    # - or return a Response object
    # - or return a Request object
    # - or raise IgnoreRequest: process_exception() methods of
    #   installed downloader middleware will be called
    return None #繼續往下走


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
    return response

def process_exception(self, request, exception, spider):
    # Called when a download handler or a process_request()
    # (from other downloader middleware) raises an exception.

    # Must either:
    # - return None: continue processing this exception
    # - return a Response object: stops process_exception() chain
    # - return a Request object: stops process_exception() chain
    pass

在中間中寫程式碼

-process_request:返回不同的物件,後續處理不同(加代理...)
  		# 1 更換請求頭
        # print(type(request.headers))
        # print(request.headers)
        #
        # from scrapy.http.headers import Headers
        # request.headers['User-Agent']=''

        # 2 加cookie ---cookie池
        # 假設你你已經搭建好cookie 池了,
        # print(request.cookies)
        # request.cookies={'username':'asdfasdf'}

        # 3 加代理
        # print(request.meta)
        # request.meta['download_timeout'] = 20
        # request.meta["proxy"] = 'http://27.188.62.3:8060'
-process_response:返回不同的物件,後續處理不同
- process_exception
def process_exception(self, request, exception, spider):
        print('xxxx')
        # 不允許直接改url
        # request.url='https://www.baidu.com'
        from scrapy import Request
        request=Request(url='https://www.baidu.com',callback=spider.parser)
        return request

selenium在scrapy中的使用流程

# 當前爬蟲用的selenium是同一個

# 1 在爬蟲中初始化webdriver物件
    from selenium import webdriver
    class CnblogSpider(scrapy.Spider):
        name = 'cnblog'
        ...
 bro=webdriver.Chrome(executable_path='../chromedriver.exe')
# 2 在中介軟體中使用(process_request)
spider.bro.get('https://dig.chouti.com/')   response=HtmlResponse(url='https://dig.chouti.com/',body=spider.bro.page_source.encode('utf-8'),request=request)
    return response
	
# 3 在爬蟲中關閉
    def close(self, reason):
        print("我結束了")
        self.bro.close()

爬蟲去重原始碼分析

from scrapy.utils.request import referer_str, request_fingerprint
def __init__(self, path=None, debug=False):
    self.file = None
    self.fingerprints = set()   建立一個集合
    self.logdupes = True
    self.debug = debug
    self.logger = logging.getLogger(__name__)
    if path:
        self.file = open(os.path.join(path, 'requests.seen'), 'a+')
        self.file.seek(0)
        self.fingerprints.update(x.rstrip() for x in self.file)
        
def request_seen(self, request):
    fp = self.request_fingerprint(request)
    if fp in self.fingerprints: # 如何在集合中,return True 不再繼續往下爬了
        return True
    self.fingerprints.add(fp)  # 如果不在集合中,把它加進去
    if self.file:
        self.file.write(fp + '\n')

def request_fingerprint(self, request):
    # 指紋去重,用的集合,將URL的地址取指紋(MD5),放到集合中
    return request_fingerprint(request)

針對大量資料的去重,可以考慮使用布隆過濾器,其的錯誤率的大小,取決於陣列的位數和雜湊函式的個數。

from scrapy.dupefilters import BaseDupeFilter
class UrlFilter(BaseDupeFilter):
    def __init__(self):
        self.bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)

def request_seen(self, request):
    if request.url in self.bloom:
        return True
    self.bloom.add(request.url)

在配置檔案中配置

 DUPEFILTER_CLASS = 'cnblogs.qc.UrlFilter' #預設的去重規則幫我們去重,去重規則在記憶體中

分散式爬蟲(scrapy-redis)

<1>安裝scrapy-redis

pip install scrapy-redis

<2>Spider繼承RedisSpider

<3>將start_urls配置到配置檔案中

# 不能寫start_urls = ['https:/www.cnblogs.com/']
需要寫 redis_key = 'myspider:start_urls' 

<4>在settings.py檔案中

# redis的連線
REDIS_HOST = 'localhost'                            # 主機名
REDIS_PORT = 6379                                   # 埠
	# 使用scrapy-redis的去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis的Scheduler
# 分散式爬蟲的配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 持久化的可以配置,也可以不配置
ITEM_PIPELINES = {
   'scrapy_redis.pipelines.RedisPipeline': 299
}

現在要讓爬蟲執行起來,需要去redis中以myspider:start_urls為key,插入一個起始地址lpush myspider:start_urls https://www.cnblogs.com/

破解知乎登陸(js逆向和解密)

"""
client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&
grant_type=password&
timestamp=1596702006088&
source=com.zhihu.web&
signature=eac4a6c461f9edf86ef33ef950c7b6aa426dbb39&
username=%2B86liuqingzheng&
password=1111111&
captcha=&
lang=en&
utm_source=&
ref_source=other_https%3A%2F%2Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F"
"""

實現流程:

# 破解知乎登陸

import requests    #請求解析庫

import base64							  #base64解密加密庫
from PIL import Image	  			      #圖片處理庫
import hmac								  #加密庫
from hashlib import sha1				  #加密庫
import time
from urllib.parse import urlencode		  #url編碼庫
import execjs							  #python呼叫node.js
from http import cookiejar as cookielib
class Spider():
    def __init__(self):
        self.session = requests.session()
        self.session.cookies = cookielib.LWPCookieJar()    #使cookie可以呼叫save和load方法
        self.login_page_url = 'https://www.zhihu.com/signin?next=%2F'
        self.login_api = 'https://www.zhihu.com/api/v3/oauth/sign_in'
        self.captcha_api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
        self.headers = {
            'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER',
        }

        self.captcha =''         #存驗證碼
        self.signature = ''	   #存簽名

    # 首次請求獲取cookie
    def get_base_cookie(self):
        self.session.get(url=self.login_page_url, headers=self.headers)

    def deal_captcha(self):
        r = self.session.get(url=self.captcha_api, headers=self.headers)
        r = r.json()
        if r.get('show_captcha'):
            while True:
                r = self.session.put(url=self.captcha_api, headers=self.headers)
                img_base64 = r.json().get('img_base64')
                with open('captcha.png', 'wb') as f:
                    f.write(base64.b64decode(img_base64))
                captcha_img = Image.open('captcha.png')
                captcha_img.show()
                self.captcha = input('輸入驗證碼:')
                r = self.session.post(url=self.captcha_api, data={'input_text': self.captcha},
                                      headers=self.headers)
                if r.json().get('success'):
                    break

    def get_signature(self):
        # 生成加密簽名
        a = hmac.new(b'd1b964811afb40118a12068ff74a12f4', digestmod=sha1)
        a.update(b'password')
        a.update(b'c3cef7c66a1843f8b3a9e6a1e3160e20')
        a.update(b'com.zhihu.web')
        a.update(str(int(time.time() * 1000)).encode('utf-8'))
        self.signature = a.hexdigest()

    def post_login_data(self):
        data = {
            'client_id': 'c3cef7c66a1843f8b3a9e6a1e3160e20',
            'grant_type': 'password',
            'timestamp': str(int(time.time() * 1000)),
            'source': 'com.zhihu.web',
            'signature': self.signature,
            'username': '+8618953675221',
            'password': '',
            'captcha': self.captcha,
            'lang': 'en',
            'utm_source': '',
            'ref_source': 'other_https://www.zhihu.com/signin?next=%2F',
        }

        headers = {
            'x-zse-83': '3_2.0',
            'content-type': 'application/x-www-form-urlencoded',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER',
        }

        data = urlencode(data)
        with open('zhih.js', 'rt', encoding='utf-8') as f:
            js = execjs.compile(f.read(), cwd='node_modules')
        data = js.call('b', data)

        r = self.session.post(url=self.login_api, headers=headers, data=data)
        print(r.text)
        if r.status_code == 201:
            self.session.cookies.save('mycookie')
            print('登入成功')
        else:
            print('登入失敗')

    def login(self):
        self.get_base_cookie()
        self.deal_captcha()
        self.get_signature()
        self.post_login_data()
if __name__ == '__main__':
    zhihu_spider = Spider()
    zhihu_spider.login()

爬蟲的反爬措施總結

1 user-agent
2 referer
3 cookie(cookie池,先訪問一次)
4 頻率限制(代理池,延遲)
5 js加密(扣出來,exjs模組指向)
6 css加密
7 驗證碼(打碼平臺),半手動
8 圖片懶載入