赞
踩
Scrapy是一个Python编写的开源网络爬虫框架。它是一个被设计用于爬取网络数据、提取结构性数据的框架。
Scrapy 使用了Twisted['twɪstɪd]异步网络框架,可以加快我们的下载速度。
Scrapy文档地址:http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html
少量的代码,就能够快速的抓取
其流程可以描述如下:
注意
注意
命令:
sudo apt-get install scrapy
或者:
pip/pip3 install scrapy
通过命令将scrapy项目的的文件生成出来,后续步骤都是在项目文件中进行相关操作,下面以抓取传智师资库来学习scrapy的入门使用:http://www.itcast.cn/channel/teacher.shtml
创建scrapy项目的命令:
scrapy startproject <项目名字>
示例:
scrapy startproject myspider
生成的目录和文件结果如下:
通过命令创建出爬虫文件,爬虫文件为主要的代码作业文件,通常一个网站的爬取动作都会在爬虫文件中进行编写。
命令:
在项目路径下执行:
scrapy genspider <爬虫名字> <允许爬取的域名>
爬虫名字: 作为爬虫运行时的参数
允许爬取的域名: 为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的url,如果爬取的url与允许的域不通则被过滤掉。
示例:
cd myspider
scrapy genspider itcast itcast.cn
生成的目录和文件结果如下:
在上一步生成出来的爬虫文件中编写指定网站的数据采集操作,实现数据提取
import scrapy class ItcastSpider(scrapy.Spider): # 继承scrapy.spider # 爬虫名字 name = 'itcast' # 允许爬取的范围 allowed_domains = ['itcast.cn'] # 开始爬取的url地址 start_urls = ['http://www.itcast.cn/channel/teacher.shtml'] # 数据提取的方法,接受下载中间件传过来的response def parse(self, response): # scrapy的response对象可以直接进行xpath names = response.xpath('//div[@class="tea_con"]//li/div/h3/text()') print(names) # 获取具体数据文本的方式如下 # 分组 li_list = response.xpath('//div[@class="tea_con"]//li') for li in li_list: # 创建一个数据字典 item = {} # 利用scrapy封装好的xpath选择器定位元素,并通过extract()或extract_first()来获取结果 item['name'] = li.xpath('.//h3/text()').extract_first() # 老师的名字 item['level'] = li.xpath('.//h4/text()').extract_first() # 老师的级别 item['text'] = li.xpath('.//p/text()').extract_first() # 老师的介绍 print(item)
注意:
解析并获取scrapy爬虫中的数据: 利用xpath规则字符串进行定位和提取
利用管道pipeline来处理(保存)数据
import json
class ItcastPipeline():
# 爬虫文件中提取数据的方法每yield一次item,就会运行一次
# 该方法为固定名称函数
def process_item(self, item, spider):
print(item)
return item
ITEM_PIPELINES = {
'myspider.pipelines.ItcastPipeline': 400
}
配置项中键为使用的管道类,管道类使用.进行分割,第一个为项目目录,第二个为文件,第三个为定义的管道类。
配置项中值为管道的使用顺序,设置的数值约小越优先执行,该值一般设置为1000以内。
命令:在项目目录下执行scrapy crawl <爬虫名字>
示例:scrapy crawl itcast
通常在做项目的过程中,在items.py中进行数据建模
在items.py文件中定义要提取的字段:
class MyspiderItem(scrapy.Item):
name = scrapy.Field() # 讲师的名字
title = scrapy.Field() # 讲师的职称
desc = scrapy.Field() # 讲师的介绍
模板类定义以后需要在爬虫中导入并且实例化,之后的使用方法和使用字典相同
job.py:
from myspider.items import MyspiderItem # 导入Item,注意路径
...
def parse(self, response)
item = MyspiderItem() # 实例化后可直接使用
item['name'] = node.xpath('./h3/text()').extract_first()
item['title'] = node.xpath('./h4/text()').extract_first()
item['desc'] = node.xpath('./p/text()').extract_first()
print(item)
注意:
对于要提取如下图中所有页面上的数据该怎么办?
回顾requests模块是如何实现翻页请求的:
scrapy实现翻页的思路:
通过爬取网易招聘的页面的招聘信息,学习如何实现翻页请求
地址:https://hr.163.com/position/list.do
思路分析:
注意:
# False表示忽略网站的robots.txt协议,默认为True
ROBOTSTXT_OBEY = False
# scrapy发送的每一个请求的默认UA都是设置的这个User-Agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
在爬虫文件的parse方法中:
......
# 提取下一页的href
next_url = response.xpath('//a[contains(text(),">")]/@href').extract_first()
# 判断是否是最后一页
if next_url != 'javascript:void(0)':
# 构造完整url
url = 'https://hr.163.com/position/list.do' + next_url
# 构造scrapy.Request对象,并yield给引擎
# 利用callback参数指定该Request对象之后获取的响应用哪个函数进行解析
yield scrapy.Request(url, callback=self.parse)
......
scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])
参数解释
meta的作用:meta可以实现数据在不同的解析函数中的传递
在爬虫文件的parse方法中,提取详情页增加之前callback指定的parse_detail函数:
def parse(self,response):
...
yield scrapy.Request(detail_url, callback=self.parse_detail,meta={"item":item})
...
def parse_detail(self,response):
#获取之前传入的item
item = resposne.meta["item"]
特别注意
proxy
,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍找到对应的input标签,输入文本点击登陆
应用场景
scrapy中start_url是通过start_requests来进行处理的,其实现代码如下
# 这是源代码 def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True)
所以对应的,如果start_url地址中的url是需要登录后才能访问的url地址,则需要重写start_request方法并在其中手动添加上cookie
测试账号 noobpythoner zhoudawei123
import scrapy import re class Login1Spider(scrapy.Spider): name = 'login1' allowed_domains = ['github.com'] start_urls = ['https://github.com/NoobPythoner'] # 这是一个需要登陆以后才能访问的页面 def start_requests(self): # 重构start_requests方法 # 这个cookies_str是抓包获取的 cookies_str = '...' # 抓包获取 # 将cookies_str转换为cookies_dict cookies_dict = {i.split('=')[0]:i.split('=')[1] for i in cookies_str.split('; ')} yield scrapy.Request( self.start_urls[0], callback=self.parse, cookies=cookies_dict ) def parse(self, response): # 通过正则表达式匹配用户名来验证是否登陆成功 # 正则匹配的是github的用户名 result_list = re.findall(r'noobpythoner|NoobPythoner', response.body.decode()) print(result_list) pass
注意:
我们知道可以通过scrapy.Request()指定method、body参数来发送post请求;但是通常使用scrapy.FormRequest()来发送post请求
注意:scrapy.FormRequest()能够发送表单和ajax请求,参考阅读 https://www.jb51.net/article/146769.htm
找到post的url地址:点击登录按钮进行抓包,然后定位url地址为https://github.com/session
找到请求体的规律:分析post请求的请求体,其中包含的参数均在前一次的响应中
否登录成功:通过请求个人主页,观察是否包含用户名
import scrapy import re class Login2Spider(scrapy.Spider): name = 'login2' allowed_domains = ['github.com'] start_urls = ['https://github.com/login'] def parse(self, response): authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first() utf8 = response.xpath("//input[@name='utf8']/@value").extract_first() commit = response.xpath("//input[@name='commit']/@value").extract_first() #构造POST请求,传递给引擎 yield scrapy.FormRequest( "https://github.com/session", formdata={ "authenticity_token":authenticity_token, "utf8":utf8, "commit":commit, "login":"noobpythoner", "password":"***" }, callback=self.parse_login ) def parse_login(self,response): ret = re.findall(r"noobpythoner|NoobPythoner",response.text) print(ret)
小技巧
在settings.py中通过设置COOKIES_DEBUG=TRUE 能够在终端看到cookie的传递传递过程
继续完善wangyi爬虫,在pipelines.py代码中完善
import json from pymongo import MongoClient class WangyiFilePipeline(object): def open_spider(self, spider): # 在爬虫开启的时候仅执行一次 if spider.name == 'itcast': self.f = open('json.txt', 'a', encoding='utf-8') def close_spider(self, spider): # 在爬虫关闭的时候仅执行一次 if spider.name == 'itcast': self.f.close() def process_item(self, item, spider): if spider.name == 'itcast': self.f.write(json.dumps(dict(item), ensure_ascii=False, indent=2) + ',\n') # 不return的情况下,另一个权重较低的pipeline将不会获得item return item class WangyiMongoPipeline(object): def open_spider(self, spider): # 在爬虫开启的时候仅执行一次 if spider.name == 'itcast': # 也可以使用isinstanc函数来区分爬虫类: con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient self.collection = con.itcast.teachers # 创建数据库名为itcast,集合名为teachers的集合操作对象 def process_item(self, item, spider): if spider.name == 'itcast': self.collection.insert(item) # 此时item对象必须是一个字典,再插入 # 如果此时item是BaseItem则需要先转换为字典:dict(BaseItem) # 不return的情况下,另一个权重较低的pipeline将不会获得item return item
在settings.py设置开启pipeline
......
ITEM_PIPELINES = {
'myspider.pipelines.ItcastFilePipeline': 400, # 400表示权重
'myspider.pipelines.ItcastMongoPipeline': 500, # 权重值越小,越优先执行!
}
......
别忘了开启mongodb数据库 sudo service mongodb start
并在mongodb数据库中查看 mongo
思考:在settings中能够开启多个管道,为什么需要开启多个?
根据scrapy运行流程中所在位置不同分为:
但在scrapy默认的情况下 两种中间件都在middlewares.py一个文件中
爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件
接下来我们对腾讯招聘爬虫进行修改完善,通过下载中间件来学习如何使用中间件
编写一个Downloader Middlewares和我们编写一个pipeline一样,定义一个类,然后在setting中开启
Downloader Middlewares默认的方法:
process_request(self, request, spider):
process_response(self, request, response, spider):
在settings.py中配置开启中间件,权重值越小越优先执行
import random
from Tencent.settings import USER_AGENTS_LIST # 注意导入路径,请忽视pycharm的错误提示
class UserAgentMiddleware(object):
def process_request(self, request, spider):
user_agent = random.choice(USER_AGENTS_LIST)
request.headers['User-Agent'] = user_agent
# 不写return
class CheckUA:
def process_response(self,request,response,spider):
print(request.headers['User-Agent'])
return response # 不能少!
DOWNLOADER_MIDDLEWARES = {
'Tencent.middlewares.UserAgentMiddleware': 543, # 543是权重值
'Tencent.middlewares.CheckUA': 600, # 先执行543权重的中间件,再执行600的中间件
}
USER_AGENTS_LIST = [
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
]
运行爬虫观察现象
proxy
字段request.meta['proxy']
免费代理ip:
class ProxyMiddleware(object):
def process_request(self,request,spider):
# proxies可以在settings.py中,也可以来源于代理ip的webapi
# proxy = random.choice(proxies)
# 免费的会失效,报 111 connection refused 信息!重找一个代理ip再试
proxy = 'https://1.71.188.37:3128'
request.meta['proxy'] = proxy
return None # 可以不写return
收费代理ip:
# 人民币玩家的代码(使用abuyun提供的代理ip)
import base64
# 代理隧道验证信息 这个是在那个网站上申请的
proxyServer = 'http://proxy.abuyun.com:9010' # 收费的代理ip服务器地址,这里是abuyun
proxyUser = 用户名
proxyPass = 密码
proxyAuth = "Basic " + base64.b64encode(proxyUser + ":" + proxyPass)
class ProxyMiddleware(object):
def process_request(self, request, spider):
# 设置代理
request.meta["proxy"] = proxyServer
# 设置认证
request.headers["Proxy-Authorization"] = proxyAuth
在使用了代理ip的情况下可以在下载中间件的process_response()方法中处理代理ip的使用情况,如果该代理ip不能使用可以替换其他代理ip
class ProxyMiddleware(object):
......
def process_response(self, request, response, spider):
if response.status != '200':
request.dont_filter = True # 重新发送的请求对象能够再次进入队列
return requst
在settings.py中开启该中间件
以github登陆为例
import scrapy
class Login4Spider(scrapy.Spider):
name = 'login4'
allowed_domains = ['github.com']
start_urls = ['https://github.com/1596930226'] # 直接对验证的url发送请求
def parse(self, response):
with open('check.html', 'w') as f:
f.write(response.body.decode())
import time from selenium import webdriver def getCookies(): # 使用selenium模拟登陆,获取并返回cookie username = input('输入github账号:') password = input('输入github密码:') options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--disable-gpu') driver = webdriver.Chrome('/home/worker/Desktop/driver/chromedriver', chrome_options=options) driver.get('https://github.com/login') time.sleep(1) driver.find_element_by_xpath('//*[@id="login_field"]').send_keys(username) time.sleep(1) driver.find_element_by_xpath('//*[@id="password"]').send_keys(password) time.sleep(1) driver.find_element_by_xpath('//*[@id="login"]/form/div[3]/input[3]').click() time.sleep(2) cookies_dict = {cookie['name']: cookie['value'] for cookie in driver.get_cookies()} driver.quit() return cookies_dict class LoginDownloaderMiddleware(object): def process_request(self, request, spider): cookies_dict = getCookies() print(cookies_dict) request.cookies = cookies_dict # 对请求对象的cookies属性进行替换
配置文件中设置开启该中间件后,运行爬虫可以在日志信息中看到selenium相关内容
在前面scrapy框架中我们已经能够使用框架实现爬虫爬取网站数据,如果当前网站的数据比较庞大, 我们就需要使用分布式来更快的爬取数据
简单的说 分布式就是不同的节点(服务器,ip不同)共同完成一个任务
scrapy_redis是scrapy框架的基于redis的分布式组件
Scrapy_redis在scrapy的基础上实现了更多,更强大的功能,具体体现在:
通过持久化请求队列和请求的指纹集合来实现:
思考:那么,在这个基础上,如果需要实现分布式,即多台服务器同时完成一个爬虫,需要怎么做呢?
在scrapy_redis中,所有的待抓取的request对象和去重的request对象指纹都存在所有的服务器公用的redis中
所有的服务器中的scrapy进程公用同一个redis中的request对象的队列
所有的request对象存入redis前,都会通过该redis中的request指纹集合进行判断,之前是否已经存入过
在默认情况下所有的数据会保存在redis中
具体流程如下:
clone github scrapy-redis源码文件
git clone https://github.com/rolando/scrapy-redis.git
研究项目自带的demo
mv scrapy-redis/example-project ~/scrapyredis-project
在domz爬虫文件中,实现方式就是之前的crawlspider
类型的爬虫
from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class DmozSpider(CrawlSpider): """Follow categories and extract links.""" name = 'dmoz' allowed_domains = ['dmoztools.net'] start_urls = ['http://dmoztools.net/'] # 这里修改了url # 定义数据提取规则,使用了css选择器 rules = [ Rule(LinkExtractor( restrict_css=('.top-cat', '.sub-cat', '.cat-item') ), callback='parse_directory', follow=True), ] def parse_directory(self, response): for div in response.css('.title-and-desc'): yield { 'name': div.css('.site-title::text').extract_first(), 'description': div.css('.site-descr::text').extract_first().strip(), 'link': div.css('a::attr(href)').extract_first(), }
但是在settings.py中多了以下内容,这几行表示scrapy_redis
中重新实现的了去重的类,以及调度器,并且使用RedisPipeline
管道类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
REDIS_URL = "redis://127.0.0.1:6379"
#或者使用下面的方式
# REDIS_HOST = "127.0.0.1"
# REDIS_PORT = 6379
继续执行程序,会发现程序在前一次的基础之上继续往后执行,所以domz爬虫是一个基于url地址的增量式的爬虫
我们从settings.py中的三个配置来进行分析
分别是:
RedisPipeline中观察process_item,进行数据的保存,存入了redis中
RFPDupeFilter 实现了对request对象的加密
scrapy_redis调度器的实现了决定什么时候把request对象加入带抓取的队列,同时把请求过的request对象过滤掉
改写网易招聘爬虫,该爬虫就是一个经典的基于url地址的增量式爬虫
打开example-project项目中的myspider_redis.py文件
通过观察代码:
__init__
方法,该方法不是必须的,可以手动指定allow_domainsscrapy crawl 爬虫名
,使该节点的scrapy_redis爬虫程序就位lpush redis_key 'start_url'
,使全部节点真正的开始运行DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
REDIS_URL = "redis://127.0.0.1:6379"
改写tencent爬虫为分布式爬虫
注意:启动方式发生改变
scrapy_splash是scrapy的一个组件
splash官方文档 https://splash.readthedocs.io/en/stable/
scrapy-splash能够模拟浏览器加载js,并返回js运行后的数据
splash的dockerfile https://github.com/scrapinghub/splash/blob/master/Dockerfile
观察发现splash依赖环境略微复杂,所以我们可以直接使用splash的docker镜像
如果不使用docker镜像请参考 splash官方文档 安装相应的依赖环境
安装参考 https://blog.csdn.net/sanpic/article/details/81984683
在正确安装docker的基础上pull取splash的镜像
sudo docker pull scrapinghub/splash
运行splash的docker服务,并通过浏览器访问8050端口验证安装是否成功
前台运行 sudo docker run -p 8050:8050 scrapinghub/splash
后台运行 sudo docker run -d -p 8050:8050 scrapinghub/splash
访问 http://127.0.0.1:8050 看到如下截图内容则表示成功
以ubuntu18.04为例
sudo vi /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
重启电脑或docker服务后重新获取splash镜像
这时如果还慢,请使用手机热点(流量orz)
需要先关闭容器后,再删除容器
sudo docker ps -a
sudo docker stop CONTAINER_ID
sudo docker rm CONTAINER_ID
pip install scrapy-splash
以baidu为例
scrapy startproject test_splash
cd test_splash
scrapy genspider no_splash baidu.com
scrapy genspider with_splash baidu.com
在settings.py文件中添加splash的配置以及修改robots协议
# 渲染服务的url
SPLASH_URL = 'http://127.0.0.1:8050'
# 下载器中间件
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# 去重过滤器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# 使用Splash的Http缓存
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
在spiders/no_splash.py中完善
import scrapy
class NoSplashSpider(scrapy.Spider):
name = 'no_splash'
allowed_domains = ['baidu.com']
start_urls = ['https://www.baidu.com/s?wd=13161933309']
def parse(self, response):
with open('no_splash.html', 'w') as f:
f.write(response.body.decode())
import scrapy from scrapy_splash import SplashRequest # 使用scrapy_splash包提供的request对象 class WithSplashSpider(scrapy.Spider): name = 'with_splash' allowed_domains = ['baidu.com'] start_urls = ['https://www.baidu.com/s?wd=13161933309'] def start_requests(self): yield SplashRequest(self.start_urls[0], callback=self.parse_splash, args={'wait': 10}, # 最大超时时间,单位:秒 endpoint='render.html') # 使用splash服务的固定参数 def parse_splash(self, response): with open('with_splash.html', 'w') as f: f.write(response.body.decode())
scrapy crawl no_splash
scrapy crawl with_splash
不使用splash
使用splash
关于scrapy_splash(截屏,get_cookies等) https://www.e-learn.cn/content/qita/800748
ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守
USER_AGENT 设置ua
DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用
ITEM_PIPELINES 管道,左位置右权重:权重值越小,越优先执行
SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同
DOWNLOADER_MIDDLEWARES 下载中间件
COOKIES_ENABLED 默认为True表示开启cookie传递功能,即每次请求带上前一次的cookie,做状态保持
COOKIES_DEBUG 默认为False表示日志中不显示cookie的传递过程
LOG_LEVEL 默认为DEBUG,控制日志的等级
LOG_FILE 设置log日志文件的保存路径,如果设置该参数,日志信息将写入文件,终端将不再显示,且受到LOG_LEVEL日志等级的限制
SPLASH_URL = 'http://127.0.0.1:8050'
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
from __future__ import absolute_import from copy import deepcopy from scrapy.utils.request import request_fingerprint from scrapy.utils.url import canonicalize_url from scrapy_splash.utils import dict_hash from scrapy_redis.dupefilter import RFPDupeFilter def splash_request_fingerprint(request, include_headers=None): """ Request fingerprint which takes 'splash' meta key into account """ fp = request_fingerprint(request, include_headers=include_headers) if 'splash' not in request.meta: return fp splash_options = deepcopy(request.meta['splash']) args = splash_options.setdefault('args', {}) if 'url' in args: args['url'] = canonicalize_url(args['url'], keep_fragments=True) return dict_hash(splash_options, fp) class SplashAwareDupeFilter(RFPDupeFilter): """ DupeFilter that takes 'splash' meta key in account. It should be used with SplashMiddleware. """ def request_fingerprint(self, request): return splash_request_fingerprint(request) """以上为重写的去重类,下边为爬虫代码""" from scrapy_redis.spiders import RedisSpider from scrapy_splash import SplashRequest class SplashAndRedisSpider(RedisSpider): name = 'splash_and_redis' allowed_domains = ['baidu.com'] # start_urls = ['https://www.baidu.com/s?wd=13161933309'] redis_key = 'splash_and_redis' # lpush splash_and_redis 'https://www.baidu.com' # 分布式的起始的url不能使用splash服务! # 需要重写dupefilter去重类! def parse(self, response): yield SplashRequest('https://www.baidu.com/s?wd=13161933309', callback=self.parse_splash, args={'wait': 10}, # 最大超时时间,单位:秒 endpoint='render.html') # 使用splash服务的固定参数 def parse_splash(self, response): with open('splash_and_redis.html', 'w') as f: f.write(response.body.decode())
# 渲染服务的url SPLASH_URL = 'http://127.0.0.1:8050' # 下载器中间件 DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } # 使用Splash的Http缓存 HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage' # 去重过滤器 # DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' # DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指纹生成以及去重类 DUPEFILTER_CLASS = 'test_splash.spiders.splash_and_redis.SplashAwareDupeFilter' # 混合去重类的位置 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 调度器类 SCHEDULER_PERSIST = True # 持久化请求队列和指纹集合, scrapy_redis和scrapy_splash混用使用splash的DupeFilter! ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400} # 数据存入redis的管道 REDIS_URL = "redis://127.0.0.1:6379" # redis的url
注意:
scrapyd是一个用于部署和运行scrapy爬虫的程序,它允许你通过JSON API来部署爬虫项目和控制爬虫运行,scrapyd是一个守护进程,监听爬虫的运行和请求,然后启动进程来执行它们
所谓json api本质就是post请求的webapi
scrapyd服务:
pip install scrapyd
scrapyd客户端:
pip install scrapyd-client
在scrapy项目路径下 启动scrapyd的命令:sudo scrapyd
或 scrapyd
启动之后就可以打开本地运行的scrapyd,浏览器中访问本地6800端口可以查看scrapyd的监控界面
点击job可以查看任务监控界面
编辑需要部署的项目的scrapy.cfg文件(需要将哪一个爬虫部署到scrapyd中,就配置该项目的该文件)
[deploy:部署名(部署名可以自行定义)]
url = http://localhost:6800/
project = 项目名(创建爬虫项目时使用的名称)
同样在scrapy项目路径下执行:
scrapyd-deploy 部署名(配置文件中设置的名称) -p 项目名称
部署成功之后就可以看到部署的项目
curl http://localhost:6800/schedule.json -d project=project_name -d spider=spider_name
curl http://localhost:6800/cancel.json -d project=project_name -d job=jobid
import requests # 启动爬虫 url = 'http://localhost:6800/schedule.json' data = { 'project': 项目名, 'spider': 爬虫名, } resp = requests.post(url, data=data) # 停止爬虫 url = 'http://localhost:6800/cancel.json' data = { 'project': 项目名, 'job': 启动爬虫时返回的jobid, } resp = requests.post(url, data=data)
Gerapy 是一款 分布式爬虫管理框架,支持 Python 3,基于 Scrapy、Scrapyd、Scrapyd-Client、Scrapy-Redis、Scrapyd-API、Scrapy-Splash、Jinjia2、Django、Vue.js 开发,Gerapy 可以帮助我们:
1.执行如下命令,等待安装完毕
pip3 install gerapy
2.验证gerapy是否安装成功
在终端中执行 gerapy 会出现如下信息
“”"
Usage:
gerapy init [–folder=]
gerapy migrate
gerapy createsuperuser
gerapy runserver [host:port]
“”"
1.新建一个项目
gerapy init
执行完该命令之后会在当前目录下生成一个gerapy文件夹,进入该文件夹,会找到一个名为projects的文件夹
2. 对数据库进行初始化(在gerapy目录中操作),执行如下命令
gerapy migrate
对数据库初始化之后会生成一个SQLite数据库,数据库保存主机配置信息和部署版本等
3.启动 gerapy服务
gerapy runserver
此时启动gerapy服务的这台机器的8000端口上开启了Gerapy服务,在浏览器中输入http://localhost:8000就能进入Gerapy管理界面,在管理界面就可以进行主机管理和界面管理
2.执行爬虫,就点击调度.然后运行. (前提是: 我们配置的scrapyd中,已经发布了爬虫.)
2. 配置Projects
5. 成功部署之后会显示描述和部署时间
6. 来到clients界面,找到部署该项目的节点,点击调度
7. 在该节点中的项目列表中找到项目,点击右侧run运行项目
补充:
1.Gerapy 与 scrapyd 有什么关联吗?
我们仅仅使用scrapyd是可以调用scrapy进行爬虫. 只是需要使用命令行开启爬虫
curl http://127.0.0.1:6800/schedule.json -d project=工程名 -d spider=爬虫名
使用Greapy就是为了将使用命令行开启爬虫变成 “小手一点”. 我们在gerapy中配置了scrapyd后,不需要使用命令行,可以通过图形化界面直接开启爬虫.
回顾之前的代码中,我们有很大一部分时间在寻找下一页的url地址或者是内容的url地址上面,这个过程能更简单一些么?
思路:
对应的crawlspider就可以实现上述需求,能够匹配满足条件的url地址,组装成Reuqest对象后自动发送给引擎,同时能够指定callback函数
即:crawlspider爬虫可以按照规则自动获取连接
scrapy genspider -t crawl job 163.com
class JobSpider(CrawlSpider):
name = 'job'
allowed_domains = ['163.com']
start_urls = ['https://hr.163.com/position/list.do']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
i = {}
#i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
#i['name'] = response.xpath('//div[@id="name"]').extract()
#i['description'] = response.xpath('//div[@id="description"]').extract()
return i
在crawlspider爬虫中,没有parse函数
重点在rules中:
通过crawlspider爬取网易招聘的详情页的招聘信息
url:https://hr.163.com/position/list.do
思路分析:
注意:连接提取器LinkExtractor中的allow对应的正则表达式匹配的是href属性的值
scrapy genspider -t crawl <爬虫名> <allowed_domail>
创建一个crawlspider的模板,页可以手动创建链接提取器LinkExtractor的更多常见参数
['hr.tencent.com', 'baidu.com']
restrict_xpaths='//div[@class="pagenav"]'
Rule常见参数
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。