scrapy Spider CrawlSpider redis 总汇以及一些源码分析

版权声明: https://blog.csdn.net/dashoumeixi/article/details/86770060
scrapy流程
scrapy 流程图

玩scrapy 时,一直看着这张图就ok.

scrapy需要的基础 : lxml ( xpath) ,  关键字 yield ( 生成器)  . 生成器我的博客中有很多文章.可以自己去找 .

下面先说一下scrapy的全貌:

一个最简单的Spider : 基本上你用scrapy创建的跟我的差不多.

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['https://dig.chouti.com/']

    def parse(self, response):
        print("return : ",response.url , response.meta)
        yield response.meta

先说一下大致的流程:

1 . Spider.start_requests  读取 start_urls  每次yield 一个Request , 这个Request 将被传递到(每个)Spider中间件(Spider middlewares,看图)  , 在Spider中间件中执行  process_start_requests  处理( yield )每个Request .接下来到了调度器中, 由调度器传递给 (每个) 下载中间件(Downloader middlewares)  , 每个下载中间件将调用 process_request (这一步往往我们会用的比较多,

比如修改Header , User-Agent, Proxy 等等) ,之后就真正去下载网页了.

2. 等到网页下载完成后, 将回到(每个) 下载中间件的  process_response ,意思是返回前你还需要对response做点什么.默认直接返回。接着再次通过 (每个)Spider中间件 的 process_spider_input ,意思是你还有次机会能处理response .最后回到 parse函数.

3.在上面的代码中有一句yield response.meta  这一步的流程是 :  通过(每个)Spider中间件的 process_spider_output 传递给

(每个) pipelines (常常用于过滤/清洗数据, 以及保存到文件或数据库) 的 process_item . 附注: item 你可以完全把他当成dict使用.

这3个步骤基本就是Scrapy的执行流程. 上面的叙述中我都使用了(每个)的意思是, 这些中间件和pipelines 都可以创建多个.

然后在settings.py 中指定其调用顺序.

比方有这么一个下载中间件:

#代理中间件 属于下载中间件
class xProxy(HttpProxyMiddleware):
    def __init__(self,encodeing):
        super().__init__(encodeing)
        self.PROXIES = ['http://183.207.95.27:80', 'http://111.6.100.99:80', 
                   'http://60.31.239.166:3128', 'http://114.55.31.115:3128']

    def process_request(self, request, spider):
        import random
        request.meta['proxy'] =random.choice(self.PROXIES)
        super().process_request(request,spider)


#指定User-agent 中间件 属于下载中间件
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
class RandomUserAgent:
    def __init__(self):
        self.user_agents = ['MSIE (MSIE 6.0; X11; Linux; i686) Opera 7.23',
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)        Chrome/45.0.2454.101 Safari/537.36"]


    def process_request(self, request, spider):
        print('->RandomUserAgent process_request')
        import random
        request.headers['User-Agent'] =  random.choice(self.user_agents)

内部2个类, 一个用于选择代理, 一个用于选择USER-AGENT .  他们2 都属于下载中间件. 

因此 . 在setting.py 中指定这样的顺序:

# DOWNLOADER_MIDDLEWARES = {
#    'testscrapy.middlewares.RandomUserAgent': 540,
#     'testscrapy.proxymiddleware.xProxy':541
# }

那么RandomUserAgent中的process_request 将先调用. 而后将调用xProxy.process_request.

这种顺序通用于scrapy中的各个组件.  比如Spider中间件, pipelines.  

在scrapy 中你会发现有很多依赖创建的函数  :  from_crawler / from_settings 类方法 ,以及process_xxx的处理函数. 大多数情况下,在后期编写过程中你也发现这些. 最后说一下扩展. 扩展依赖信号.

比如:

from scrapy import signals
from scrapy.crawler import Crawler

class Extention :
    def __init__(self,crawler):
        self.crawler = crawler
        self.settings = crawler.settings

    
    #scrapy将使用 from_crawler 来依赖创建对象
    @classmethod
    def from_crawler(cls, crawler):

        #创建这个扩展
        obj = cls(crawler)
        
        #通过信号来调用
        #如果spider 打开时
        crawler.signals.connect(obj.opened , signal=signals.spider_opened)
        #如果spider 结束
        crawler.signals.connect(obj.closed,signal=signals.spider_closed)
        #如果spider 有一item 被pipeline接受了
        crawler.signals.connect(obj.item_pushed,signal=signals.item_scraped)

        return obj


    def item_pushed(self,item,spider,response):
        print('Extention item pushed : ', item, spider.name ,response)
    def opened(self,spider):
        print('Extention opened name:%s'%spider.name)

    def closed(self,spider):
        print('Extention closed name:%s'%spider.name)

到此为止. 基本的scrapy全貌已经说完;

正式的教程:

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'            # 爬虫名字 , 全局唯一
    allowed_domains = []       #允许的域名, 空为都可以爬取,如果填上chouti.com,则只能爬chouti

    start_urls = ['https://dig.chouti.com/']    #父类start_requests将循环这个变量


    #当然你可以修改父类的start_requests
    #源码类似于
#    def start_requests(self):
#        for url in self.start_urls:
#           yield url

    def parse(self, response):                #访问完成后将回到这个默认函数中
        print("return : ",response.url , response.meta)
        # yield response.meta

        #call_back 指定回调函数 . 默认情况parse 
        yield Request("https://www.baidu.com",callback=self.process_baidu)

    def process_baidu(self , response):
        print("return : ", response.url, response.meta)

上面你唯一需要注意的就是 name ,全局唯一, scrapy 启动将读取spiders 下的文件. 根据你所指定的 name 来启动爬虫.

只与name字段有关,与类名,文件名都无关

可以自己再次发出请求 , 并指定要处理结果的函数(callback) . 由于twisted机制, 你需要使用yield 而不是 return ;

把这些函数当成生成器来使用.

pipelines: 用于数据存储 

初学时只需要写一个函数即可 : process_item .

当然里面还有open_spider  当spider被开启时做点事, from_crawler 可以根据配置文件来创建这个对象等等.具体可查看文档.

一个最简单的 pipeline :

class pipeline1(object):
    def process_item(self, item, spider):
        print('pipeline1 process item :  ' , item)
        return item

下面把item, pipeline, 以及主程序都整合一下:

main.py:

import scrapy
from ..items import TestscrapyItem
class ChoutiSpider(scrapy.Spider):
    name = 'go'
    allowed_domains = []
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml#ac']
    def parse(self, response):
        lis = response.xpath('//div[@class="li_txt"]')
        for li in lis:
            item = TestscrapyItem()
            item['name'] = li.xpath('./h3/text()').extract_first().strip()
            item['title'] = li.xpath('./h4/text()').extract_first().strip()
            item['desc'] = li.xpath('./p/text()').extract_first().strip()
            yield item

注意了 yield item . 将把这个词典流向 spider 中间件, 继而传递到 pipeline 的process_item 中.

items.py: 你可以把它当成dict使用

import scrapy


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

再来一个main.py : 多几个例子

class ChoutiSpider(scrapy.Spider):
    name = 'go'
    allowed_domains = []
    url = 'https://hr.tencent.com/position.php?&start='
    
    #默认情况下, start_requests 会循环 start_urls
    start_urls = (ChoutiSpider.url+str(i) for i in range(0,11,10))

    def parse(self, response):
        for tr in response.xpath("//tr[@class='even'] | //tr[@class='odd']"):
             item = TestscrapyItem()
             item['name'] = tr.xpath('./td[1]/a/text()').extract_first()
             item['link'] = tr.xpath('./td[1]/a/@href').extract_first()
             item['type'] = tr.xpath('./td[2]/text()').extract_first()
             item['num'] = tr.xpath('./td[3]/text()').extract_first()
             item['location'] = tr.xpath('./td[4]/text()').extract_first()
             item['publishtime'] = tr.xpath('./td[5]/text()').extract_first()
             yield item

item.py

class TestscrapyItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    name = scrapy.Field()
    link = scrapy.Field()
    type = scrapy.Field()
    num = scrapy.Field()
    location = scrapy.Field()
    publishtime= scrapy.Field()

ok . 到此为止继承Spider的类已经基本解决;

下面写一个下载斗鱼图片的东西: 使用了 ImagesPipeline

对于图片/文件下载非常简单, scrapy 已经给你做了各种封装. 

只要配置一下就ok

第一个例子: 完全使用scrapy 内置的功能

settings.py :

#下载目录
IMAGES_CACHE = "D:/code/py/testscrapy/testscrapy/douyucache/"
ITEM_PIPELINES = {
   'scrapy.pipelines.images.ImagesPipeline' : 1 #配置专门用于下载图片的pipeline
}

main.py

#这里你唯一需要做的就是 yield 一个词典,包含 image_urls就ok

class douyuSpider(scrapy.Spider):
    name = 'douyu'
    allowed_domains = []
    
    #douyu 返回图片的json
    url = 'http://capi.douyucdn.cn/api/v1/getVerticalRoom?limit=20&offset='
    offset = 0
    start_urls = [url+str(offset)]
    def parse(self, response):
        res = json.loads(response.text,encoding='utf-8')['data']

        #获取图片链接
        image_urls = (part['vertical_src'] for part in res)
        
        #只需要传递一个image_urls字段即可
        yield {"image_urls" : image_urls }

现在可以运行一下.

现在看一下源码:

    

#这里只列出几个重要的
#以下2个函数都可以重写

#这个函数用于去发请求下载
    def get_media_requests(self, item, info):
       1. self.images_urls_field  默认情况下就是 “image_urls" 
       2. 如果不想使用 "image_urls" 使用自己的怎么办?
            
       3.源码中: 
        if not hasattr(self, "IMAGES_URLS_FIELD"):
            self.IMAGES_URLS_FIELD = self.DEFAULT_IMAGES_URLS_FIELD #“image_urls”


        这里就展示了我们可以进行配置
        self.images_urls_field = settings.get(
            resolve('IMAGES_URLS_FIELD'),
            self.IMAGES_URLS_FIELD
        )
        你唯一需要做的就是在配置文件中加一个 IMAGES_URLS_FIELD = "img_paths"
        这样的话. 在main.py中就可以写成 : yield {"img_paths" : xxxx}        
        
        
        return [Request(x) for x in item.get(self.images_urls_field, [])]

#在下载完成/失败后将被调用.将把下载的结果默认情况下存储在 dict['images'] 中
    def item_completed(self, results, item, info):
        1.self.images_result_field 默认情况下是 "images"
        2.根据源码,可以在配置文件中使用 IMAGES_RESULT_FIELD = "img_result"
           来自定义返回字段


        if isinstance(item, dict) or self.images_result_field in item.fields:
            item[self.images_result_field] = [x for ok, x in results if ok]
        return item

下面重写一下 这2个函数.

现在需不需要在settings.py 中定义  IMAGES_URLS_FIELD . IMAGES_RESULT_FIELD 随你自己的意愿

例子:

settings.py 新增加了点东西

IMAGES_STORE = "D:/code/py/testscrapy/testscrapy/douyucache/"
IMAGES_URLS_FIELD = "img_urls"        #自定义字段. 放置url的地方。
IMAGES_RESULT_FIELD = "img_res"       #放置结果的地方
IMAGES_EXPIRES = 0                    #每次都重新下载


ITEM_PIPELINES = {
    #重写的pipeline
    'testscrapy.pipelines.downloadImgPipeline': 300,
    #打印一下
    'testscrapy.pipelines.printPipeline': 301
   # 'scrapy.pipelines.images.ImagesPipeline' : 1
}

ROBOTSTXT_OBEY = False

items.py


import scrapy


class TestscrapyItem(scrapy.Item):
    # define the fields for your item here like:
    nickname = scrapy.Field()    #暂时不用
    img_urls = scrapy.Field()    #pipeline中将读取这个字段
    img_res = scrapy.Field()    #pipeline 下载完成后将填充这个字段
    room_id = scrapy.Field()    #暂时不用

main.py

class douyuSpider(scrapy.Spider):
    name = 'douyu'
    allowed_domains = []
    url = 'http://capi.douyucdn.cn/api/v1/getVerticalRoom?limit=20&offset='
    offset = 0
    start_urls = [url+str(offset)]
    def parse(self, response):
        res = json.loads(response.text,encoding='utf-8')['data']

        for part in res:
            item = TestscrapyItem()
            item['nickname'] = part['nickname']
            item['room_id'] = part['room_id']
            item['img_urls'] = (part['vertical_src'],) #这里是一个tuple或list
            yield item

pipelines.py  最重要的一部分:

# -*- coding: utf-8 -*-

import os
from scrapy.http.request import Request
from scrapy.pipelines.images import ImagesPipeline
from scrapy.utils.project import get_project_settings
from scrapy.exceptions import DropItem
#创建目录下载
#get_project_settings 获取当前settings.py
os.makedirs(get_project_settings()['IMAGES_STORE'],exist_ok=True)

#重写用于下载图片的pipeline
class downloadImgPipeline(ImagesPipeline):
    #以下2个函数基本跟源码一致.除非用于测试或要修改文件名,否则没必要重写
    def get_media_requests(self, item, info):
        #images_urls_field 就是配置文件的 img_urls
        return [Request(url) for url in item.get(self.images_urls_field, [])]

    def item_completed(self, results, item, info):
        #images_result_field 就是img_res
        if isinstance(item, dict) or self.images_result_field in item.fields:
            item[self.images_result_field] = [x for ok, x in results if ok]
            if not item[self.images_result_field]:
                #DropItem 将不会把item 传递到下一个pipeline中
                raise DropItem("图片下载出错")
        return item


#打印一下
class printPipeline(object):
    def process_item(self, item, spider):
        if spider.name == 'douyu':
            #打印 item_completed 存放的结果
            print("->> :" , item['img_res'])



现在整个pipeline已经自定义完成. 不过这样性能没一次性给pipeline的好.

比如在main.py中可以自定义一个 data = {"data" : [item, item]}  , 然后一次性yield data. 当然如果这样,

你就需要自己修改上面2个重写的函数.工作量也不大.

LinkExtractor : 提取网页中的链接

熟悉lxml的可以直接使用response.xpath 来提取

LinkExtractor 更简单.

例子:

from scrapy.linkextractors import LinkExtractor
class linkSpider(scrapy.Spider):
    name = 'link'
    allowed_domains = []
    offset = 0
    start_urls = ['https://hr.tencent.com/position.php']
    def parse(self, response):
        #构建一个LinkExtractor 直接调用其唯一一个接口extract_links
        links = LinkExtractor().extract_links(response)
        for link in links:    #这样所有链接全出来了
            print(link)        

使用正则:

from scrapy.linkextractors import LinkExtractor
class linkSpider(scrapy.Spider):
    name = 'link'
    allowed_domains = []
    offset = 0
    start_urls = ['https://hr.tencent.com/position.php']
    def parse(self, response):
        #在构造函数中添加一个allow=正则
        links = LinkExtractor(allow=(r'start=\d+')).extract_links(response)
        for link in links:    #这样所有链接全出来了
            print(link)        

LinkExtractor 一般来说用于Rule对象.而Rule对象一般在通用Spider中使用.

通用Spider  就是 CrawlSpider .继承Spider,

这种爬虫类用来主要是简化操作的.比如,你爬 https://hr.tencent.com/position.php 下面所有的分页.并同时进入其中的每个页面的每个链接,那么就是使用CrawlSpider. 当然,你完全可以使用上面一般的爬虫去做.

下面是第一个例子:

要使用通用爬虫需要多加一个参数,比如执行命令 scrapy genspider -t crawl  tencent tencent.com:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class TencentSpider(CrawlSpider):
    name = 'tencent'
    allowed_domains = ['tencent.com']
    start_urls = ['https://hr.tencent.com/position.php']

    rules = (
        #callback : 爬取此页面,完成后调用 parse_item 来处理,注意是字符串
        #follow : 意味是否爬取后面N页,直到爬完所有符合LinkExtract匹配的页面
        Rule(LinkExtractor(allow=r'start=\d+'), callback='parse_item', follow=False),
    )

    def parse_item(self, response):
        print('parse_item:',response.url)

建议在settings.py中加:

LOG_LEVEL = 'DEBUG'

能看清整个过程

这样你写一个规则,就是拿到所有的页面.不需要再一个个yield Request了

注意,在使用通用爬虫时,不要再重写parse了. 自己随意定义一个函数名即可, parse在父类中有别的用处

类似这种爬虫,一般统称为深度爬虫.往往这种爬虫深得我心~~~ 写几个规则去爬取 然后喝一个咖啡.

其基本流程如下:

1.先请求 start_urls ,返回response后调用Rule中的LinkExtract去匹配, 

2.如果能匹配到, LinkExtract 将返回一个列表 , 然后一个个 yield Request , 返回response后,

如果follow = True则再次用Rule去进行匹配,由于上面代码中follow = False.因此,此时将不再匹配,调用callback;

3.总之, 每次response返回后都将使用 Rule进行匹配,除非follow = False.

下面使用2个Rule来分别爬取横向页面以及详细页面

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import TestscrapyItem

class TencentSpider(CrawlSpider):
    name = 'news'
    allowed_domains = ['wz.sun0769.com']
    start_urls = ['http://wz.sun0769.com/html/top/reply.shtml']

    #定义了2个规则. 第一个规则 在response返回后抓取第一个页面的其他分页 (2,3,4,5,6....)
    #第二个规则抓取response的每个页面, 再次抓取所有的链接并发起请求,同时
    #设置了回调函数来分析详细页面

    rules = (
        Rule(LinkExtractor(allow=r'(reply\?page=\d+)'),follow=False),
              Rule(LinkExtractor(allow=r'(question/\d+/\d+.shtml$)'),callback='parse_item',follow=False),
    )

    #处理每个详细页面
    def parse_item(self, response):
        td = response.xpath('//td[@valign="middle"]')[0]
        title , id = td.xpath('./child::node()/text()').extract()
        id = id.strip()
        content = response.xpath('//div[@class="contentext"]/text()')
        if content:
            content = ''.join(content.extract()).strip()
        else:
            content = response.xpath('//td[@class="txt16_3"]/text()')
            content = ''.join(content.extract()).strip()
        url = response.url
        yield TestscrapyItem(id=id,title=title,url=url,content=content)


再次重复一下 : CrawlSpider中

所有定义的Rule 在每次response响应后都会进行匹配,除非follow = False.

在上面的例子.第一次response返回后, 首先使用第一个规则获取所有链接并进行访问.

再执行第二个规则匹配所有链接,并进行访问.

现在假设 第一个规则所匹配的第一个链接返回response,由于第一个规则的follow = False,因此不会再次匹配.

然后使用第二个规则匹配.

总之: 所有的response返回后都将使用 2 个规则进行匹配.

另外说一下. scrapy shell 后面加 http://xxxx.com  用于测试xpath于测试代码很好用.

下面直接使用Spider 来做上面干的事.

# -*- coding: utf-8 -*-

from scrapy.http.request import Request
from scrapy.utils.project import get_project_settings
import random
import scrapy
import json
from ..items import TestscrapyItem
from scrapy.linkextractors import LinkExtractor
class douyuSpider(scrapy.Spider):
    name = 'sun'
    allowed_domains = ['wz.sun0769.com']
    url = 'http://wz.sun0769.com/index.php/question/reply?page='
    offset = 0
    start_urls = [url+str(offset)]
    def parse(self, response):
        print(response.url)
        #拿分页.这里不使用这种方式
        # page_links = LinkExtractor(allow=r'(reply\?page=\d+)').extract_links(response)
        #拿每个详细链接
        detail_links = LinkExtractor(allow=r'(question/\d+/\d+.shtml$)').extract_links(response)

        #对每个详细链接发请求
        for link in detail_links:
            yield Request(link.url,callback=self.parse_detail_page)

        #控制一下拿取的数量
        if self.offset > 20:
            raise StopIteration()

        self.offset += 30
        
        #callback 默认parse; 别跟CrawlSpider 搞混了
        yield Request(self.url+str(self.offset))

    #跟上面CrawlSpider的实现一样
    def parse_detail_page(self,response):
        td = response.xpath('//td[@valign="middle"]')[0]
        title , id = td.xpath('./child::node()/text()').extract()
        id = id.strip()
        content = response.xpath('//div[@class="contentext"]/text()')
        if content:
            content = ''.join(content.extract()).strip()
        else:
            content = response.xpath('//td[@class="txt16_3"]/text()')
            content = ''.join(content.extract()).strip()
        url = response.url
        yield TestscrapyItem(id=id,title=title,url=url,content=content)






关于表单数据: FormRequest

一般来说可以直接使用Request来完成 . scrapy 提供了一个简单的类 FormRequest 也可以完成相同的功能:

发送请求时将携带cookie信息

from scrapy.http import FormRequest
class LoginSpider(scrapy.Spider):
    name = 'gologin'
    allowed_domains = []
    start_urls = ['http://example.webscraping.com/places/default/user/login']

    def parse(self, response ):
        sel = response.xpath("//div[@style]/input")
        formdata = dict(zip(sel.xpath('./@name').extract() , sel.xpath('./@value').extract()))
        formdata['email'] = "你的邮箱"
        formdata['password'] = "你的密码"
        yield FormRequest('http://example.webscraping.com/places/default/user/login',
                          formdata=formdata,callback=self.login)



    def login(self,response):
        print(response)


一个更简单的类方法: from_response. 将携带<input type="hidden">一起发送请求

from scrapy.http import FormRequest
class ChoutiSpider(scrapy.Spider):
    name = 'gologin'
    allowed_domains = []
    start_urls = ['http://example.webscraping.com/places/default/user/login']

    def parse(self, response ):
        yield FormRequest.from_response(response,{"email":"邮箱","password":"密码"},
                                        callback=self.login)



    def login(self,response):
        print(response)

关于去重:

默认情况下,已经有去重规则了: scrapy.dupefilters.RFPDupeFilter

自己定义:

from scrapy.utils.request import request_fingerprint
from scrapy.dupefilters import BaseDupeFilter


class TestDup(BaseDupeFilter):
    def __init__(self):
        print("dup start")
        self.visited_url = set() #set 去重 
    @classmethod
    def from_settings(cls, settings):
        return cls()

    def request_seen(self, request):
        encrypt_url = request_fingerprint(request) 把url 加密,固定字符串长度

        print(encrypt_url , request.url)

        if encrypt_url in self.visited_url: #如果已经访问过,则True 表示不再yield Request
            return True
        self.visited_url.add(encrypt_url)

        return False

    def open(self):  # can return deferred
        print('dup filter open')

    def close(self, reason):  # can return a deferred
        print('dup filter close')

    def log(self, request, spider):  # log that a request has been filtered
        pass

基于redis去重:

from scrapy.utils.request import request_fingerprint
from scrapy.dupefilters import BaseDupeFilter
import redis
import threading

def singleton(cls):
    instance = None
    lock = threading.Lock()
    def oncall(*args, **kwargs):
        nonlocal  instance
        if not instance:
            with lock:
                if not instance :
                    instance = cls(*args, **kwargs)
        return instance
    return oncall


@singleton
class redisConnPool:
    def __init__(self,host="localhost",port=6379,password=""):
        self.__pool = redis.ConnectionPool(max_connections=1000,
                                           host=host,port=port,password=password)

    def pool(self):
        return self.__pool

class TestDup(BaseDupeFilter):
    def __init__(self):
        self.pool = redisConnPool()
        self.cli = redis.StrictRedis(connection_pool=self.pool.pool())
        self.visited_url = "visited_url"

    #host, port ,passwd 可以在settings中配置,通过这个函数来构造
    @classmethod
    def from_settings(cls, settings):
        return cls()

    def request_seen(self, request):
        encrypt_url = request_fingerprint(request) #加密后固定长度

        #如果是1 则代表加入成功, 0则原集合中存在
        if 1 == self.cli.sadd(self.visited_url,encrypt_url):
            return False
        return True


关于代理:

如果环境变量中有 http_proxy / https_proxy 则使用.如果没有则查看request.meta['proxy']中是否有代理

例如, 在脚本中启动scrapy:

from scrapy.cmdline import execute
if __name__ == "__main__":
    import os
                                       #帐号:密码@代理:端口
    os.environ['http_proxy'] = "http://nihao:[email protected]:11080"
    os.environ['https_proxy'] = "https://nihao:[email protected]:11080"
    execute(["scrapy","crawl","douban250"])

在settings.py中开启:

HTTPPROXY_ENABLED = True
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware':543
}

在 HttpProxyMiddleware 源代码中 __init__ 将读取到self.proxies中:


#例如:
self.proxies = {'http': (b'bmloYW86cGFzc3dk', 'http://127.0.0.1:11080'), 'https': (b'bmloYW86cGFzc3dk', 'https://127.0.0.1:11080')}

在  process_request 首先查看request.meta['proxy']中是否有代理,如果没有则使用 self.proxies中的代理,

如没有则不使用代理 ; 另外如果目标地址是http则使用http代理,https则使用https代理.

总的来说,默认代理不太好用,都会自定义代理 . 其根本就在request.meta['proxy']中添加代理即可, 如果有帐号密码则使用HttpProxyMiddleware._basic_auth_header 来加密并设置request.headers['Proxy-Authorization'] = b'Basic ' + 帐号密码

即可;

比较简单暴力的一种代理设置:

class xProxy(HttpProxyMiddleware):
    def __init__(self,encodeing):
        super().__init__(encodeing)
        
        self.PROXIES = ['http://183.207.95.27:80', 'http://111.6.100.99:80', 
                   'http://60.31.239.166:3128', 'http://114.55.31.115:3128']

    def process_request(self, request, spider):
        import random
        #直接在meta['proxy'] 设置 , 然后调用父类的处理方式
        request.meta['proxy'] =random.choice(self.PROXIES)
        super().process_request(request,spider)

如果有密码的情况下:

import random
from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware
class RandomProxy(HttpProxyMiddleware):
    def __init__(self,encoding):
        super().__init__(encoding)
        #假设有这些代理
        self.proxy_list = ['http://183.207.95.27:80', 'http://111.6.100.99:80']
    def process_request(self, request, spider):
        #如果已经有了proxy 则直接交与父类处理,当然也可以自己直接处理.这里偷懒.
        if 'proxy' in request.meta:
            super().process_request(request,spider)
        else:
            #随机选择一个
            proxy = random.choice(self.proxy_list)
            
            # 调用父类的分割用户名密码与ip , 同时加密用户名与密码
            creds, proxy_url = self._get_proxy(proxy,None)
            request.meta['proxy'] = proxy_url
            if creds:
                request.headers['Proxy-Authorization'] = b'Basic ' + creds

settings.py

DOWNLOADER_MIDDLEWARES = {
    'test_scrapy.middlewares.RandomProxy': 545,
}

HTTPPROXY_ENABLED = True

猜你喜欢

转载自blog.csdn.net/dashoumeixi/article/details/86770060