爬虫框架
一. 基本使用
1.1 安装 scrapy
pip3 install scrapy
window: 有些会报错, 如果报错,解决方案如下:
1、pip3 install wheel # 安装后,便支持通过wheel文件安装软件 xx.whl
3、pip3 install lxml
4、pip3 install pyopenssl
5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
7、执行pip3 install “6”中的下载目录
8、pip3 install scrapy
1.2 创建及启动命令
1. 创建爬虫项目
scrapy startproject myfirstscrapy # myfirstscrapy 是项目名称
2. 创建爬虫 【相当于 django 中的 app】
scrapy genspider name urlofwebsite # name: 名称, urlofwebsite: 爬取地址
3. 启动爬虫
scrapy crawl name --nolog # name: 名称, --nolog: 不打印日志(可选)
4.将爬虫遵循协议关掉 # 《settings.py》
将 ROBOTSTXT_OBEY = True 改成 ROBOTSTXT_OBEY = False
5. 非命令式启动爬虫
根目录创建 run.py 《目录结构中可见》, 内容如下:
# <文件名随意, main.py 也可, 最后以脚本形式运行即可>
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'name','--nolog']) # 列表里是启动爬虫的命令
1.3 项目目录结构
├──NewsPro # 项目名
├── NewsPro # 项目包名
│ ├── __init__.py
│ ├── items.py # 类似于django的 models表模型,一个个模型类
│ ├── middlewares.py # 中间件
│ ├── pipelines.py # 管道---》写持久化
│ ├── settings.py # 项目配置文件
│ └── spiders # 里面放了自定义的爬虫,类似于app
│ ├── __init__.py
│ ├── huanqiu.py # 自定义爬虫文件
│ └── wangyi.py # 自定义爬虫文件
│── scrapy.cfg # 项目上线配置
│── run.py # 运行文件 《需要自己创建》
二. scrapy 爬虫架构
1. 引擎(EGINE)
引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。
2. 调度器(SCHEDULER)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列,
由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
3. 下载器(DOWLOADER)
用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
4. 爬虫(SPIDERS)--->在这里写代码
SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求
5. 项目管道(ITEM PIPLINES)
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
6. 下载器中间件(Downloader Middlewares)
6.1 位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,
6.2 可用该中间件做以下几件事:设置请求头,设置cookie,使用代理,集成selenium
7. 爬虫中间件(Spider Middlewares)
位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
三. scrapy解析数据 (css <<>> xpath)
具体见 https://www.cnblogs.com/gjjcode/articles/17379737.html中的 13
语句解析: https://www.cnblogs.com/gjjcode/articles/18111434.html
语法 | 作用 |
---|---|
.//a[contains(@class,"link-title")]/text() | xpath取文本内容 |
.//a[contains(@class,"link-title")]/@href | xpath取属性 |
a.link-title::text | css取文本 |
img.image-scale::attr(src) | css取属性 |
.extract_first() | 取一个 |
.extract() | 取所有 |
使用
# css 使用
response.css('article.post-item')
# xpath 使用
response.xpath('//article[contains(@class,"post-item")]')
四. settings 相关配置
4.1 基础配置
1. 是否遵循爬虫协议 <基本上需要改成 false>
ROBOTSTXT_OBEY = False
2. 爬虫项目名字
BOT_NAME = 'myfirstscrapy'
3. 指定爬虫类的py文件的位置
SPIDER_MODULES = ['myfirstscrapy.spiders']
NEWSPIDER_MODULE = 'myfirstscrapy.spiders'
4. 日志级别
LOG_LEVEL='ERROR' # 报错如果不打印日志,在控制台看不到错误,默认是 INFO 级别
5. USER_AGENT # 请求头
# 自定义
from fake_useragent import UserAgent
USER_AGENT = UserAgent().random
# 框架默认
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
6. DEFAULT_REQUEST_HEADERS 默认请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
7. SPIDER_MIDDLEWARES 爬虫中间件
SPIDER_MIDDLEWARES = {
'cnblogs.middlewares.CnblogsSpiderMiddleware': 543,
}
8. DOWNLOADER_MIDDLEWARES 下载中间件
DOWNLOADER_MIDDLEWARES = {
'cnblogs.middlewares.CnblogsDownloaderMiddleware': 543,
}
9. ITEM_PIPELINES 持久化配置
ITEM_PIPELINES = {
'cnblogs.pipelines.CnblogsPipeline': 300,
}
4.2 高效配置 (增加爬虫的爬取效率)
1. 增加并发:默认16
# 默认scrapy开启的并发线程为32个,可以适当进行增加。
CONCURRENT_REQUESTS = 100 # 值为100,并发设置成了为100。
2. 降低日志级别:
# 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。
LOG_LEVEL = 'INFO'
3. 禁止cookie:
# 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。
COOKIES_ENABLED = False
4. 禁止重试:
# 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。
RETRY_ENABLED = False
5. 减少下载超时:
# 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。
DOWNLOAD_TIMEOUT = 10 超时时间为10s
五. 持久化
5.1 持久化方案
方式一: 《本地文件存储》 (了解)
# 简单代码示例
def parse(self, response):
items = []
for element in response.css('some_selector'):
item = {
'k1': v1,
'k2': v2,
}
items.append(item)
return items # 将列表 return 出去
# 持久化命令
在控制台中执行下面命令《需要按照上面代码示例书写》
1. """JSON 格式"""
# scrapy crawl 爬虫项目名 -o 存储的文件路劲
scrapy crawl your_spider_name -o ./output.json
2. """CSV 格式"""
scrapy crawl your_spider_name -o ./output.csv -t csv
# 格式有以下几种(json ', 'jsonlines ','jsonl',"jl','csv ','xml ',‘marshal', 'pickle')
方式二 《利用框架中的管道》(重要)
使用pipline 常用的,管道形式,可以同时存到多个位置的
1. 在 items.py 中写一个类[相当于写django的表模型],继承 scrapy.Item, 在类中写属性,写字段,所有字段都是scrapy.Field类型
import scrapy
# 字段是爬虫中要保存的数据
class CnBlogsItem(scrapy.Item):
art_title = scrapy.Field()
user_avatar = scrapy.Field()
desc = scrapy.Field()
2. 在爬虫中导 1 中的类,实例化得到对象,把要保存的数据放到对象中
from .... import CnBlogsItem
# 实例化一个对象
item = CnBlogsItem()
# 将要保存的数据保存到 item 对象中
item['数据变量名'] = 数据变量名 # eg: item['title'] = title
# 并将 item return 出去, 但不是用 return, 是用 yield
yield item
3. 修改配置文件,指定pipline,数字表示 执行 优先级,数字越小优先级越大
# 一开始是被注释掉的, 需要自己解开。
ITEM_PIPELINES = {
# 对应 4 中类的所在路径
'crawl_cnblogs.pipelines.MyfirstscrapyPipeline_1': 300,
'crawl_cnblogs.pipelines.MyfirstscrapyPipeline_2': 301,
'crawl_cnblogs.pipelines.MyfirstscrapyPipeline_3': 302,
}
4. 在 piplines.py 文件中写一个 MyfirstscrapyPipeline 类, 《一开始会给一个》
class MyfirstscrapyPipeline_1:
# 这里在开始时会自动触发
def open_spider(self, spider):
"""
数据初始化,打开文件,打开数据库链接
做数据保存之前的工作
"""
def process_item(self, item, spider):
"""
做数据保存工作
"""
# 这里必须还回 item
return item
# 这里在结束时会自动触发
def close_spider(self, spider):
"""
销毁资源,关闭文件,关闭数据库链接
做数据保存之后的工作
"""
class MyfirstscrapyPipeline_2:
pass
class MyfirstscrapyPipeline_3:
pass
5.2 爬取案列
1. 爬虫文件
import scrapy
from myfirstscrapy.items import CnBlogsItem
# 如果不是太理解 Request,可以点击看源码,或百度搜索。
from scrapy import Request
class CnblogsSpider(scrapy.Spider):
name = "cnblogs"
allowed_domains = ["www.cnblogs.com"]
start_urls = ["https://www.cnblogs.com"]
def parse(self, response):
# 相应的数据作处理
article_list = response.xpath('//article[contains(@class, "post-item")]')
for article in article_list:
item = CnBlogsItem()
art_title = article.css('a.post-item-title::text').extract_first()
user_avatar = article.css('img.avatar::attr(src)').extract_first()
article_desc = article.css('p.post-item-summary::text').extract()
desc = article_desc[0].replace('\n', '').replace(' ', '')
if not desc:
desc = article_desc[1].replace('\n', '').replace(' ', '')
url = article.css('a.post-item-title ::attr(href)').extract_first()
# 将数据保存到 item 对象中
item['art_title'] = art_title
item['user_avatar'] = user_avatar
item['desc'] = desc
"""
这里因为想要的数据还未爬取完,所以不能直接 yield item,(即不能直接保存数据)
利用 Request 继续进行爬取
url: 要爬取的 url 地址
callback: 回调函数, 在此函数里面对新爬取的数据进行处理, 数据完整后再进行保存,
meta: 因为爬虫是异步的, 请求和响应的顺序是不一致的, 导致数据错乱, 以 meta 做标记
《 还有其他的参数 》
"""
# 《1》. 进入详情页进行内容爬取 callback 函数要注意。
yield Request(url=url, callback=self.detail_parse, meta={'item': item})
# 《3》. 每爬完一页, 就需要进行下一页,进行请求。callback 函数要注意。
next_url = 'https://www.cnblogs.com' + response.css('div.pager > a:last-child::attr(href)').extract_first()
print(next_url, '888')
yield Request(url=next_url, callback=self.parse)
# 《2》. 对详情页的结构进行数据处理, 执行完后, 这一页的每个文章内容就爬取完了, 最后 yield item 就会进如管道进行数据持久化。
def detail_parse(self, response):
url = response.url
item = response.meta.get('item')
article_content = response.css('div.post').extract_first()
item['url'] = url
item['article_content'] = article_content
# 数据完整后对数据进行保存
yield item
2. items.py 文件
import scrapy
class CnBlogsItem(scrapy.Item):
# 下面是保存数据对应的字段
art_title = scrapy.Field()
user_avatar = scrapy.Field()
desc = scrapy.Field()
url = scrapy.Field()
article_content = scrapy.Field()
3. pipelines.py 文件
★★★ MyfirstscrapyPipeline 类一定不要忘记在 settings.py 中注册 ★★★
import pymysql
class MyfirstscrapyPipeline:
1. 进行数据库连接初始化
def open_spider(self, spider):
self.conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='*******',
passwd='*******',
db='cnblogs',
)
self.cursor = self.conn.cursor(
cursor=pymysql.cursors.DictCursor
)
2. 数据保存数据库中的操作
def process_item(self, item, spider):
my_sql = 'insert into article (art_title, user_avatar, `desc`,url, article_content) values (%s, %s,%s, %s, %s)'
# self.cursor.execute(my_sql, args=[item['art_title'], item['user_avatar'], item['desc']])
self.cursor.execute(my_sql, (
item['art_title'], item['user_avatar'], item['desc'], item['url'], item['article_content']))
self.conn.commit()
return item
3. 数据处理之后,对数据库进行关闭操作
def close_spider(self, spider):
self.cursor.close()
self.conn.close()
六. 中间件
6.1 爬虫中间件
# 爬虫中间件(用的很少,了解即可)
MyfirstscrapySpiderMiddleware
def process_spider_input(self, response, spider): # 进入爬虫会执行它
pass
def process_spider_output(self, response, result, spider): # 从爬虫出来会执行它
pass
def process_spider_exception(self, response, exception, spider): # 出了异常会执行
pass
def process_start_requests(self, start_requests, spider): # 第一次爬取执行
pass
def spider_opened(self, spider): # 爬虫开启执行
pass
6.1 下载中间件
# 下载中间件
MyfirstscrapyDownloaderMiddleware
def process_request(self, request, spider): # request对象从引擎进入到下载器会执行
pass
def process_response(self, request, response, spider): # response对象从下载器进入到引擎会执行
pass
def process_exception(self, request, exception, spider): # 出异常执行它
pass
def spider_opened(self, spider): # 爬虫开启执行它
pass
""" 重点
process_request,process_response
"""
# 下载中间件的process_request
-返回值:
- return None: # 继续执行下面的中间件的process_request
- return a Response object: # 不进入下载中间件了,直接返回给引擎,引擎把它通过6给爬虫
- return a Request object: # 不进入中间件了,直接返回给引擎,引擎把它放到调度器中
- raise IgnoreRequest: process_exception() # 抛异常,会执行process_exception
# 下载中间件的process_response
-返回值:
- return a Response object: # 会进入到引擎,引擎把它给爬虫
- return a Request object: # 会进入到引擎,引擎把它放到调度器中,等待下次爬取
- raise IgnoreRequest # 会执行process_exception
七. 加代理,cookie,header,selenium
7.1 加代理
1. 中间件中加代理
'''获取代理函数'''
def get_proxy(self):
import requests
res=requests.get('http://192.168.1.143:5010/get/').json()
if res.get('https'):
return 'https://'+ res.get('proxy')
else:
return 'http://' + res.get('proxy')
'''中间件'''
def process_request(self, request, spider):
# request 就是咱们在解析中yiedl的Request的对象
# spider 就是爬虫对象
my_pro = self.get_proxy()
request.meta['proxy'] = my_pro
print(request.meta)
return None
2. 代理可能不能用, 则会触发 process_exception 异常处理 中间件,
def process_exception(self, request, exception, spider):
print(request.url) # 这个地址是没有爬的地址
return request
7.2 加 cookie
1. 中间件中加cookie
def process_request(self, request, spider):
print(request.cookies)
request.cookies['name']='lqz'
return None
7.3 加请求头
1. 中间件中加请求头
def process_request(self, request, spider):
print(request.headers)
request.headers['referer'] = 'http://www.lagou.com'
return None
7.4 动态生成 User-agent 使用
1. 使用 fake_useragent 模块
2. 导入:
from fake_useragent import UserAgent
def process_request(self, request, spider):
# 实例化对象
ua = UserAgent()
"""
print(ua.ie) # 随机打印ie浏览器任意版本
print(ua.firefox) # 随机打印firefox浏览器任意版本
print(ua.chrome) # 随机打印chrome浏览器任意版本
print(ua.random) # 随机打印任意厂家的浏览器
"""
# 加入 User-agent
request.headers['User-Agent']=str(ua.random)
print(request.headers)
return None
7.5 集成 selenium
1. 在爬虫类中写
from selenium import webdriver
class CnblogsSpider(scrapy.Spider):、
# 初始 selenium
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.implicitly_wait(10)
def parse(self, response):
# yield 这里 yield 后会进去下面的 process_request 中。
pass
def close(self, spider, reason):
# 浏览器关掉
self.bro.close()
2. 中间件
def process_request(self, request, spider):
"""
1. 因为有的页面需要执行 js, 所以请求回来的数据并完整, 因此需要使用 selenium,
但有的不需要, 因此对不同的页面做不同的处理 (不同的爬取方式)。
2.
2.1 selenium 爬取方式。
2.2 框架自带的爬取方式。
"""
if request.meta.get('is_entirely'):
# 发送请求
spider.bro.get(request.url)
# 包装成一个 response 对象, 返回出去
# 这里body需要 bytes 格式, 而 spider.bro.page_source 是字符串, 所以需要转一下
response = HtmlResponse(url=request.url, body=bytes(spider.bro.page_source, encoding='utf-8'))
return response
else:
return None
八. 布隆过滤器
九. scrapy-redis实现分布式爬虫
1. 第一步:安装 scrapy-redis >>> pip3 install scrapy-redis
2. 第二步:改造爬虫类
from scrapy_redis.spiders import RedisSpider
class CnblogSpider(RedisSpider):
name = 'cnblog_redis'
allowed_domains = ['cnblogs.com']
# 写一个key:redis列表的key,起始爬取的地址, 第五步会用到。
redis_key = 'myspider:start_urls'
3. 第三步:配置文件配置
"""
分布式爬虫配置
去重规则使用redis
"""
REDIS_HOST = 'localhost' # 主机名
REDIS_PORT = 6379 # 端口
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 看了源码
SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 先进先出:队列,先进后出:栈
# 持久化:文件,mysql,redis
ITEM_PIPELINES = {
'cnblogs.pipelines.CnblogsFilePipeline': 300,
'cnblogs.pipelines.CnblogsMysqlPipeline': 100,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
4. 第四步:在多台机器上启动scrapy项目,在一台机器起了多个scrapy爬虫进程,就相当于多台机器
-进程,线程,协程。。。
-进程间数据隔离 IPC
5. 第五步:把起始爬取的地址放到redis的列表中
# mycrawler:start_urls 是 第二步 中的 key
lpush mycrawler:start_urls http://www.cnblogs.com/