玩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