爬虫05--Scrapy框架
作者:互联网
1 scrapy 介绍安装
# 1.介绍
通用的网络爬虫框架, 爬虫界的django,也可用于如数据挖掘、监测和自动化测试等领域
Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架 (性能比较高的框架)。
因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。
# 2.安装
mac/linux: pip3 install scrapy # 直接安装
windows: # 少部分人成功不了
1.pip3 install wheel
# 安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
2.pip3 install lxml
3.pip3 install pyopenssl
4.下载并安装pywin32:
https://github.com/mhammond/pywin32/releases
5.下载twisted的wheel文件:
http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
6.执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
7.pip3 install scrapy
2 scrapy 架构目录
# 整体架构
# 5大组件
爬虫(SPIDERS):开发人员自定义的类,用来解析responses和提取items,或者发送新的请求request
引擎(EGINE):大总管,负责控制数据的流向
调度器(SCHEDULER):由它来决定下一个要抓取的网址是什么,去重
-深度优先 (先将本链接所有层爬取完,再顶层的页面)
-广度优先 (先将本页面所有内容爬取完,再下一层)
下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
# 2大中间件
爬虫中间件(Spider Middlewares):位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入和输出(用的很少)
下载中间件(Downloader Middlewares):引擎和下载器之间,加代理,加头,集成selenium
# 开发者只需要在固定的位置写固定的代码即可(写的最多的SPIDERS、ITEM PIPLINES 和 Downloader Middlewares)
# 目录介绍
firstscrapy # 项目名字
-firstscrapy # 包
-spiders # 所有的爬虫文件放在里面
-baidu.py # 一个个的爬虫(以后基本上都在这写东西)
-chouti.py
-middlewares.py # 中间件(爬虫/下载中间件 都写在这)
-pipelines.py # 持久化相关写在这(items.py中类的对象)
-items.py # 一个一个的类,相当于models.py
-settings.py # 配置文件
-main.py # 自己加的,执行爬虫
-scrapy.cfg # 上线部署相关
3 scrapy 基本使用
# 1 新建项目
scrapy startproject 项目名
# eg: scrapy startproject firstscrapy
# 2 创建爬虫(创建app) 进入到项目中 运行
scrapy genspider 爬虫名 爬虫地址
# eg: scrapy genspider chouti dig.chouti.com
一执行就会在spider文件夹下创建出一个py文件,名字叫chouti
# 3 运行爬虫
scrapy crawl chouti # 执行爬虫,打印日志
scrapy crawl chouti --nolog # 执行爬虫,不打印日志
# 4 支持右键执行爬虫 (使用脚本运行爬虫)
-在项目路径下新建一个main.py
from scrapy.cmdline import execute
execute(['scrapy','crawl','chouti','--nolog'])
4 scrapy 解析数据(***)
# 重点
# 1 response对象有css方法和xpath方法 # 或者 自己使用第三方 bs、lxml等来解析
response.selector.css()、response.selector.xpath()
可简写为
response.css('css选择器')
response.xpath('xpath选择器')
# 2 Xpath 和 css选择器 获取文本和属性
# xpath取文本内容 /text()
.//a[contains(@class,"link-title")]/text()
# xpath取属性 /@属性名
.//a[contains(@class,"link-title")]/@href
# css取文本 ::text
a.link-title::text
# css取属性 ::attr(属性名)
img.image-scale::attr(src)
# 3 从selector对象中解出内容
选择器解析后是标签元素,内容需要提取出来
.extract_first() # 取一个 返回是字符串
.extract() # 取所有 返回是列表形式
4.1 案例--cnblogs.py
# eg: 获取博客园 首页的 新闻标题、摘要、头像等
import scrapy
class CnblogsSpider(scrapy.Spider):
name = 'cnblogs' # 爬虫名
allowed_domains = ['www.cnblogs.com'] # 允许爬取的域
start_urls = ['http://www.cnblogs.com/'] # 爬取的起始地址
def parse(self, response): # 响应对象response,从中解析出想要的数据
# print('--------------',response)
# print(response.text) # 可以使用bs4解析
############ css
article_list=response.css('.post-item') # 查出所有类名为post-item的标签,取多条
# print(len(article_list))
for article in article_list:
title=article.css('div.post-item-text>a::text').extract_first() # 取一条
# article.css('section>div>a::text')
href=article.css('div.post-item-text>a::attr(href)').extract_first()
author=article.css('a.post-item-author>span::text').extract_first()
desc=article.css('p.post-item-summary::text').extract_first()
# 取不到就是None
photo=article.css('p.post-item-summary>a>img::attr(src)').extract_first()
print(title)
print(href)
print(author)
print(desc)
print(photo)
print('----------')
########### xpath
article_list = response.xpath('//*[@class="post-item"]') # 查出所有类名为post-item的标签,取多条
# print(len(article_list))
for article in article_list:
# 注意:使用 . 从当前标签下找
title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first() # 取一条
href = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
author=article.xpath('.//a[@class="post-item-author"]/span/text()').extract_first()
desc=article.xpath('.//div[@class="post-item-text"]/p/text()').extract_first()
photo=article.xpath('.//p//img[@class="avatar"]/@src').extract_first()
# photo = article.xpath('./section/div/p/a/img/@src').extract_first()
print(title)
print(href)
print(author)
print(desc)
print(photo)
print('----------')
5 setting 相关配置
5.1 基本配置
# 1 是否遵循爬虫协议
ROBOTSTXT_OBEY = False
# 2 爬虫中间件
SPIDER_MIDDLEWARES = {
'myfirst_crawl.middlewares.MyfirstCrawlSpiderMiddleware': 543,
}
# 3 下载中间件
DOWNLOADER_MIDDLEWARES = {
'myfirst_crawl.middlewares.MyfirstCrawlDownloaderMiddleware': 543,
}
# 4 请求客户端类型 (用户代理)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
# 5 日志级别 (比--nolog好在,如果出错,控制台会报错)
LOG_LEVEL='ERROR' # 默认是 INFO ,上线改成 ERROR
5.2 提高爬虫效率
# 1 增加并发:
默认scrapy开启的并发线程为16个,可以适当进行增加。
在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
# 2 降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。
可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = 'ERROR'
# 3 禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。
在配置文件中编写:COOKIES_ENABLED = False
# 4 禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。
在配置文件中编写:RETRY_ENABLED = False
# 5 减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。
在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
6 持久化方案(***)
# 从内存写到硬盘上的过程就叫持久化
爬取了页面----》清洗出数据了----》写到硬盘上(文件,mysql)
6.1 重点
# 通用方法(pipline)
1 在items.py中写一个类,继承scrapy.Item # 相当于 装菜的盘子,
class CnblogsArticleItem(scrapy.Item):
2 在类中写类属性
title = scrapy.Field()
3 在爬虫中导入item类,实例化得到对象,把要保存的数据放到item对象中
item = CnblogsArticleItem()
item['title'] = title # 把数据放进盘子中
在最后 yield item # 每条数据构建完之后,若不把item yield出来,循环获取完成后,item数据就变成只是最后一条数据
4 修改配置文件,指定pipline,可以指定多个 (真正存数据的地方 数字表示优先级,越小越高) # 指定盘子的菜,放在哪 (文件、数据库)
ITEM_PIPELINES = {
'crawl_cnblogs.pipelines.CrawlCnblogsPipeline': 300,
...
}
5 写一个pipline类:CrawlCnblogsPipeline
open_spider(self, spider): # spider开始运行前,执行
数据初始化,打开文件,打开数据库链接
self.f=open() # 同一个类中,其他方法要使用该变量,可放在对象中
process_item(self, item, spider):
真正存储的地方
# 若是打开文件或数据库连接的操作,放进该函数,会一直重复打开,消耗资源
# 也可能会导致数据只是最后一条 (因为文件重复打开后,进行了覆盖操作)
self.f.write()
return item # 一定不要忘了return item,交给后续的pipline继续使用
close_spider(self, spider): # spider运行结束后,执行
销毁资源,关闭文件,关闭数据库链接
self.f.close()
7 全栈爬取cnblogs文章
# 爬取下一页:
解析出来,yield Request(next),就会继续爬取
# 爬取文章详情
解析出地址
详情获取回来的解析函数,不一样
7.1 request和response对象传递参数
# 1 参数存放在request对象中 # 请求地址 回调执行函数 传递参数
Request(url=url, callback=self.parse_detail,meta={'item':item})
# 2 在response对象中,获取
item=response.meta['item']
7.2 解析出下一页地址,并继续爬取
next='https://www.cnblogs.com'+response.css('.pager a:last-child::attr(href)').extract_first()
print(next)
# yield Request(url=next,callback=self.parse)
yield Request(url=next)
7.3 解析出详情页地址,并使用其他解析函数爬取
def parse(self, response):
article_list = response.xpath('//*[@class="post-item"]')
for article in article_list:
href = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
item['href'] = href
# 抛出requests对象,让解析器继续去解析爬取 且可传递参数 item对象
yield Request(url=href, callback=self.parse_detail, meta={'item': item})
def parse_detail(self, response):
item = response.meta['item']
content = response.css('#cnblogs_post_body').extract_first()
item['content'] = content
# print('文章标题:', item['title'])
yield item # 抛出item对象,让item Pipepine去存数据
8 爬虫中间件和下载中间件
# 1 爬虫和下载中间件要使用,需要在配置文件中配置
SPIDER_MIDDLEWARES = {
'crawl_cnblogs.middlewares.CrawlCnblogsSpiderMiddleware': 5,
}
DOWNLOADER_MIDDLEWARES = {
'crawl_cnblogs.middlewares.CrawlCnblogsDownloaderMiddleware': 5,
}
8.1 下载中间件的定义
# 1 写在middelwares.py中,写个类
# 2 类中写方法
-process_request(self, request, spider):
-返回 None,继续进入下一个中间件
-返回 request对象,会被引擎放到调度器,等待下一次被调度执行
-返回 response对象,会被引擎放到spider中,解析数据
-这里可以干什么事?
-修改请求头
-修改cookie
-加代理
-加selenium
-process_response(self, request, response, spider)
8.2 下载中间件的应用
# 应用:给requests对象 添加 cookie、ip代理、header 和 集成selenium
0 在下载中间件的process_reqeust方法中
# 1 加cookie
request.cookies['name']='lqz'
request.cookies= {}
# 2 修改header (token认证、ua等)
request.headers['Auth']='asdfasdfasdfasdf'
request.headers['USER-AGENT']='ssss'
# 拓:fake_useragent模块,可以随机生成user-aget
from fake_useragent import UserAgent
ua = UserAgent()
print(ua.ie) # 随机打印ie浏览器任意版本
print(ua.firefox) # 随机打印firefox浏览器任意版本
print(ua.chrome) # 随机打印chrome浏览器任意版本
print(ua.random) # 随机打印任意厂家的浏览器
# 3 加代理(从代理池中获取)
request.meta['proxy']='http://103.130.172.34:8080'
# 4 集成selenium (因为有的页面,需要js才加载数据,用原来的下载器不会执行,使用selenium就会执行js,数据更全)
-在爬虫类中类属性
driver = webdriver.Chrome(executable_path='')
-在爬虫类中方法:
def close(spider, reason):
spider.driver.close()
-在中间件中的process_reqeust中
from scrapy.http import HtmlResponse
spider.driver.get(url=request.url) # selenium get访问url
# 返回scrapy的response对象(url, body,request)
response=HtmlResponse(url=request.url,body=spider.driver.page_source.encode('utf-8'),request=request)
return response
9 源码分析--去重规则
9.1 scrapy源码分析--去重
目的:爬过一个url地址后,后期如果再爬取该地址,就不爬了---》使用集合
# 1 使用了集合去重 (元素可存可取)
# 2 默认使用的去重类:
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
# 3 后期你可以自己写一个类,替换掉内置的去重
-自定义去重类:
# 继承BaseDupeFilter,重写 request_seen()
def request_seen(self, request):
fp = self.request_fingerprint(request) # 将请求进行生成指纹
-返回True,就不爬了
-返回False,就继续爬取
-使用布隆过滤器 BloomFilter:
极小内存,快速校验是否重复
# 4 生成指纹,会把下面两种地址生成一样的指纹(本质是把?后面的参数排序,再生成指纹)
www.baidu.com?name=lqz&age=18
www.baidu.com?age=18&name=lqz
# 上面两个地址,若是直接放进集合中,会判定成两个地址,但实质,这两个是同一个地址请求
9.2 布隆过滤器
# 布隆过滤器:极小内存,快速校验是否重复,
元素可存,不可取,只用来判断一个元素是否在一个集合中
# 原理:
BloomFilter 会开辟一个m位的bitArray(位数组),开始所有数据全部置 0。
当一个元素过来时,能过多个哈希函数(h1,h2,h3....)计算不同的在哈希值,
并通过哈希值找到对应的bitArray下标处,将里面的值 0 置为 1 。
关于多个哈希函数,它们计算出来的值必须 [0,m) 之中。
当来查找对应的值时,同样通过哈希函数求值,再去寻找数组的下标,
如果所有下标都为1时,元素存在。当然也存在错误率。
(如:当数组全部为1时,那么查找什么都是存在的),
但是这个错误率的大小,取决于数组的位数和哈希函数的个数
# 博客地址:
https://www.cnblogs.com/xiaoyuanqujing/protected/articles/11969224.html
https://developer.aliyun.com/article/773205
########### python中使用布隆过滤器
# 安装
# https://www.lfd.uci.edu/~gohlke/pythonlibs/#bitarray
pip3 install bitarray-xx.whl
pip3 install pybloom-live
# 示例一:
# ScalableBloomFilter 可以自动扩容 常用
from pybloom_live import ScalableBloomFilter
bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001,mode=ScalableBloomFilter.LARGE_SET_GROWTH)
url = "www.cnblogs.com"
url2 = "www.liuqingzheng.top"
bloom.add(url)
bloom.add(url2)
print(url in bloom)
print(url2 in bloom)
# 示例二:
# BloomFilter 定长
from pybloom_live import BloomFilter
bf = BloomFilter(capacity=1000)
url='www.baidu.com'
bf.add(url)
print(url in bf)
print("www.liuqingzheng.top" in bf)
9.3 redis实现布隆过滤器
# 需要编译安装 redis 插件 Redisbloom
# 使用redis实现布隆过滤器
import redis
client = redis.Redis()
size = 10000
count = 0
client.execute_command("bf.reserve", "lqz", 0.001, size) # 新增
for i in range(size):
client.execute_command("bf.add", "lqz", "xxx%d" % i)
result = client.execute_command("bf.exists", "lqz", "xxx%d" % (i + 1))
if result == 1:
print(i)
count += 1
print("size: {} , error rate: {}%".format(size, round(count / size * 100, 5)))
10 scrapy-redis实现分布式爬虫
https://www.cnblogs.com/liuqingzheng/p/16005882.html
# 分布式: 将整个任务,分成多个步骤,每个步骤实现不同的小任务,并部署在多个机器上。
# scrapy 实现分布式爬虫的原理:
原来scrapy的Scheduler维护的是本机的任务队列(存放Request对象及其回调函数等信息)
+本机的去重队列(存放访问过的url地址),
所以实现分布式爬取的关键就是,找一台专门的主机上运行一个共享的队列比如Redis,
然后重写Scrapy的Scheduler,让新的Scheduler到共享队列存取Request,并且去除重复的Request请求,
所以总结下来,实现分布式的关键就是三点:
1.共享队列
2.重写Scheduler,让其无论是去重还是任务都去访问共享队列
3.为Scheduler定制去重规则(利用redis的集合类型)
# 1.安装scrapy-redis
# 2 在配置文件中添加配置
# 使用scrapy_redis的Scheduler替换掉原来的Scheduler
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 使用scrapy_redis的RFPDupeFilter替换原来的去重规则
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# piplie,持久化的方法
ITEM_PIPELINES = { # 数字越小,优先级越高
'scrapy_redis.pipelines.RedisPipeline': 400, # 将分布式存储到redis中
# 'myfirst_crawl.pipelines.MySqlPipeline': 300,
'myfirst_crawl.pipelines.FilePipeline': 200,
}
# 默认就是本地(可以不写)
# REDIS_HOST = 'localhost'
# REDIS_PORT = 6379
# 3.改造spider 在原来的基础上,爬虫类继承RedisSpider
from scrapy_redis.spiders import RedisSpider
class CnblogsSpider(RedisSpider):
name = 'cnblogs_redis' # 爬虫名
allowed_domains = ['www.cnblogs.com'] # 允许爬取的域
redis_key = 'myspider:start_urls' # 设置redis中 存取起始页的key
# 以后爬虫启动后,并不会开始爬取,因为没有起始地址
# 起始地址 需要自己手动写到redis中:redis-cli lpush myspider:start_urls http://www.cnblogs.com/
# 4.在不同机器上启动 (并不会立马开始,因为没有给定起始页)
scrapy crawl cnblogs_redis
# 5.在redis中写入:
redis-cli lpush myspider:start_urls http://www.cnblogs.com/
# 以后关闭任何一个机器节点,都不影响,即便重启,接着之前的继续爬取
# 总源码不到1000行,有兴趣可以看
11 爬虫其他知识
1 抓包工具:fiddler(windows),青花瓷(Mac),抓http的包,发http包,模拟网络慢
2 抓手机包
3 js逆向,加密方式一定是js写的
原理:就是在前端中,一点点倒退找到网站的该js加密方法,调用它的,或者自己重新模仿它,写一个使用
username=2333&password=111--js加密-->asdfasdfasdfasfdasfasdf
4 手机端爬虫
-appnium---》类似于selenium---》操作手机模拟人的行为
-app端,是java加密---》对apk进行反编译成java代码---》看懂加密算法
重写加密,传入你想要的值,得到可以请求的值,发送http请求
-app端,用java代码调用c语言代码写的 so,dll库 (动态链接库) 加密
反编译--》java.xxx()--->动态调试---》把so反编译---》汇编语言--->动态看数据变化---》猜逻辑
补充
# mysql 编码
-utf8:mysql的utf8格式,不是真正的utf-8,它是mysql自己定制的格式,使用两个字节存储一个字符,存表情文字,存不进去,报错了
-utf8mb4:才是真正的utf-8,使用最大四个字节存储一个字符
# 关于编码问题
http://www.liuqingzheng.top/others/1/01-ASCII%E7%A0%81%EF%BC%8CUnicode%E5%92%8CUTF-8%E7%BC%96%E7%A0%81/
# 面试题
1 python 的函数参数传递是值传递还是引用传递
-分类型:
-如果是可变类型,引用
-如果是不可变类型,copy了一份参数,传递
-可变类型和不可变类型,其他语言没有这个东西
标签:05,--,爬虫,item,Scrapy,print,article,response,scrapy 来源: https://www.cnblogs.com/Edmondhui/p/16369378.html