目录
通过downloader middleware集成selenium
selenium集成到scrapy,就是通过downloader middleware对request进行修改,使request的请求工作由原来的scrapy处理,改变为selenium处理,也就是selenium接管scrapy的request。这里可以举一反三,以后集成什么东西,可以考虑通过middleware来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
from selenium import webdriver from scrapy.http import HtmlResponse # 使用downloader middleware集成selenium # 名字随便起 class JSPageMiddleware(object): # process_request核心方法对request进行改造 # request不走scrapy的流程,走selenium的 def process_request(self, request, spider): # 不是所有的spider都需要使用selenium # 可以指定某些spider,或url走selenium # 这里以jobbole spider为例 if spider.name == 'jobbole': # 实例Chrome driver驱动 brower = webdriver.Chrome(executable_path='D:/chromedriver.exe') # 请求网址 brower.get(request.url) import time time.sleep(3) print('访问网址:{0}'.format(request.url)) # 这里必须返回response,表示request请求已结束 # 否则,会继续走scrapy的request流程,就会再request多一次 """ 需要的参数: url:请求的地址(selenium请求的url:brower.current_url) body:请求到的网页内容(selenium请求得到的网页原码:brower.page_source) encoding:字符编码 request:request对象 """ return HtmlResponse(url=brower.current_url, body=brower.page_source, encoding='utf-8', request=request) |
去settings.py,注册一下:
1 2 3 |
DOWNLOADER_MIDDLEWARES = { 'ArticleSpider.middlewares.JSPageMiddleware': 1, } |
jobbole.py,jobbole爬虫文件可以简单编写一下:
1 2 3 4 5 6 7 8 9 10 11 |
import scrapy class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] def parse(self, response): # 仅配合selenium集成到scrapy的测试 # 这里不写更多的代码 pass |
运行一下main.py:
1 2 3 4 5 6 7 8 9 |
from scrapy.cmdline import execute import os import sys #把项目根目录路径加入到python文件路径查找列表中 sys.path.append(os.path.abspath(os.path.dirname(__file__))) #拼写命令,交给scrapy.cmdline的execute去转化为cmd命令执行 execute(['scrapy','crawl','jobbole']) |
不要每个请求都新开一个Chrome
上面的代码,每请求一个网址,就会重新打开一个Chrome的浏览器,这样很耗费性能。为什么每请求一个url,就要新开一个Chrome浏览器呢?这是因为,每运行一次webdriver.Chrome(executable_path='D:/chromedriver.exe')
,就会开一个Chrome浏览器。那么可不可以多个url请求,共用一个Chrome浏览器里呢?很简单,只需在初始化的时候,对webdriver.Chrome(executable_path='D:/chromedriver.exe')
只实例化(调用)一次就行了,每个url请求共用一个实例对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from selenium import webdriver from scrapy.http import HtmlResponse class JSPageMiddleware(object): # 初始化 def __init__(self): # 初始化 只创建一次实例对象, # 后面的方法都使用这个实例对象, # 解决每个url请求,都需要另外打开一个Chrome浏览器的问题 self.brower = webdriver.Chrome(executable_path='D:/chromedriver.exe') super(JSPageMiddleware,self).__init__() def process_request(self, request, spider): if spider.name == 'jobbole': # 请求网址,使用共有的实例对象 self.brower.get(request.url) import time time.sleep(3) print('访问网址:{0}'.format(request.url)) return HtmlResponse(url=self.brower.current_url, body=self.brower.page_source, encoding='utf-8', request=request) |
信号量:middleware调用spider的方法
上面的例子,会带来一个新的问题,就是爬取结束了,Chrome浏览器不会自动关闭。可以通过scrapy生命周期函数spider_close()
来执行浏览器关闭的代码。每当爬虫结束,spider_close()
都会被调用,问题是,middleware里是无法调用spider_close()
,这个需要用到信号量。这时候,注意到def process_request(self, request, spider):
里接收的参数有一个spider,我们可以把webdriver.Chrome(executable_path='D:/chromedriver.exe')
的只有一次实例,放到spider里,那么有多少个spider就打开多少个Chrome浏览器,所以,我们把 def __init__(self):
移到对应的spider里去,这里的spider是jobbole:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import scrapy # 引入selenium的驱动 from selenium import webdriver # 引入信号量分发器 from scrapy.xlib.pydispatch import dispatcher # 引入信号量 from scrapy import signals class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] # 初始化 def __init__(self): # 初始化 只创建一次实例对象, # 后面的方法都使用这个实例对象, self.brower = webdriver.Chrome(executable_path='D:/chromedriver.exe') super(JobboleSpider, self).__init__() # 当触发了指定的信号,执行自定义的处理函数 # dispatcher.connect(自定义处理函数,触发的信号量) # signals里有很多信号量,选择合适的信号量 dispatcher.connect(self.spider_closed,signals.spider_closed) # 触发信号时的处理函数 def spider_closed(self): print('sppider closed') # 关闭浏览器 self.brower.quit() def parse(self, response): pass |
middlewares.py的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from scrapy.http import HtmlResponse class JSPageMiddleware(object): def process_request(self, request, spider): if spider.name == 'jobbole': # 请求网址,通过使用spider里的只创建一次的共有驱动实例对象 # spider.brower,以下都是 spider.brower.get(request.url) import time time.sleep(3) print('访问网址:{0}'.format(request.url)) return HtmlResponse(url=spider.brower.current_url, body=spider.brower.page_source, encoding='utf-8', request=request) |
现在可以运行main.py试一下。
selenium通过scrapy实现异步
selenium通过scrapy实现异步,是需要改写scrapy的downloader下载器的,需要熟悉scrapy downloader的接口API与编码规范。这里有一个github上的案例,仅供参考:https://github.com/flisky/scrapy-phantomjs-downloader,也可以查看scrapy的官方文档。