scrapy 爬虫
1.普通scrapy爬虫知识
1.scrapy cmd命令
1.scrapy shell 就可以开始运行脚本了
开启了scrapy 脚本运行环境列:
2.scrapy fetch http://www.baidu.com 打印网页源码
3.scrapy view https://www.biadu.com 下载网页源代码 并用浏览器打开 可查看是否动态加载非常好用
4.创建和运行
创建一个项目: scrapy startproject 项目名 ---> scrapy genspider 文件名 起始url
运行 : 1.scrapy crawl 项目名 2. 进入到项目根目录 scrapy runspider 文件名.py
5.csv json 类的通过命令保存
直接在运行时输入 改变保存格式
scrapy crawl spdiername -o name.csv
3.创建一个start.py
from scrapy.cmdline import execute
execute('scrapy crawl manhua'.split()) 运行就可以了
2.普通scrapy
1.scrapy 中xpath的结果:
[<Selector xpath='//div[@class="co_content2"]/ul/a' data='<a> <img src="/images/m.jpg" width="180"'>]
scrapy 中xpath() 后的结果是一个 selector 对象列表 可以继续 xpath
extract() 的作用是 取出 data中的数据 组成一个列表
.extract_first() 取列表的第一个元素 extract()[i]取列表第i个元素
.extract_first(default=’’) 如果找不到则默认为 ...
2.其他知识
response .body 表示取得二进制文件 response.text 也可以出来是页面源码 然后可以用正则解析
3.注意事项
1.注意item=实例化的位置 每循环一次(产生一个新的值)都要实例化一个新的item
2.yeild item:放在最后一次循环后面 每实例化一个新的item 提交一次
3.通过meta传参方法
scrapy.Request(meta={'abc':item}) 接受参数 item=response.meta['abc']
4.from copy import deepcopy 当for 循环下 需要多个item数据时 可使用 deepcopy 复制一个新的空间
5.重写__init__方法时 需要继承父类 super(之类名,self).__init__()
3.管道类pipelines
1.利用管道类添加数据
# 在管道中可以添加一些数据 给item
class AqiPipeline(object):
def process_item(self, item, spider):
item['crawl_time'] = datetime.now()
item['spider_name']=spider.name
return item
2.JSON管道 CSV类似
from scrapy.exporters import JsonItemExporter, CsvItemExporter
class Aqi Json/Csv Pipeline(object):
def open_spider(self, spider):
self.file = open('aqi.json/csv', 'wb')
self.writer = Json/Csv ItemExporter(self.file)
self.writer.start_exporting()
def process_item(self, item, spider):
self.writer.export_item(item)
return item
def close_spider(self, spider):
self.writer.finish_exporting()
self.file.close()
3.Redis 管道
import redis
class AqiRedisPipeline(object):
def open_spider(self, spider):
# 连接redis
self.conn = redis.StrictRedis(host='127.0.0.1', port=6379)
# 存放的key 名
self.save_key = 'Aqi_Redis'
def process_item(self, item, spider):
self.conn.lpush(self.save_key, item)
return item
4.Msql 管道
import pymysql
class mysqlPipeline(object):
conn = None
cursor = None
def open_spider(self, spider):
self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='', db='spider')
print('数据库连接成功')
def process_item(self, item, spider):
self.cursor = self.conn.cursor()
sql = 'insert into 糗事 values ("%s","%s") ' % (item['user'], item['content'])
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e:
print(e)
self.conn.rollback()
return item
def close_spider(self, spider):
self.cursor.close()
self.conn.close()
print('爬虫完毕')
4.图片管道之高级用法:
from scrapy.pipelines.images import ImagesPipeline
from scrapy.http import Request
from .settings import IMAGES_STORE
import os
class ManhuaPipeline(ImagesPipeline):
name=0
def get_media_requests(self, item, info):
image_url_list = item['url_list']
for url in image_url_list:
yield Request(url=url)
def item_completed(self, results, item, info):
old_paths = [x['path'] for ok, x in results if ok]
for path in old_paths:
self.name+=1
old_path=IMAGES_STORE +path
new_path = item['name']+'/'+str(self.name) + '.jpg'
print(new_path+'下载完成')
try:
os.renames(old_path, new_path)
except Exception as e:
print('错误!!')
print(item['name'] + '下载完成!!')
return item
4.setting
记得开启各种 管道 和中间件........
LOG_LEVEL = 'WARNING' #ERROR
ROBOTSTXT_OBEY = False
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
默认16
# CONCURRENT_REQUESTS = 32
2.特殊scrapy
1.处理动态网页
可以由两个方法拦截动态网页的request 然后由process_request process_response 返回新的 response
#spider中
from selenium import webdriver
#在spider中先实例化 浏览器对象
def __init__(self):
self.br=webdriver.Chrome()
# middlewares 中间件中
from time import sleep
from scrapy.http import HtmlResponse
class AqiDownloaderMiddleware(object):
# 通过判断enging 传过来的 request 对象的url 是不是动态页面的url
# 如果是 拦截对应的response 返回新de自己构建的 response
def process_request(self, request, spider):
url=request.url
if 'month=2' in url:
br=spider.br
br.get(url=url)
sleep(2)
data=br.page_source
return HtmlResponse(url=request.url,body=data,request=request,encoding='utf-8')
return None
2. 处理UA池,代理池
利用 process_request 拦截 request 在 中间件 交给下载器 之前 给请求 带上 headers 和设置代理
import random
class SpiderDownloaderMiddleware(object):
UA_list = []
proxy_http_list = []
proxy_https_list = []
def process_request(self, request, spider):
if request.url.split(':')[0]== 'http':
request.meta['proxy']=random.choice(self.proxy_http_list)
else:
request.meta['proxy']=random.choice(self.proxy_https_list)
request.meta['headers'] = random.choice(self.UA_list)
requet.headers['User-Agent']=random.choice(self.UA_list)
return None
3. 处理 post请求 三种方法
1.cookie登录
重写父类 start_requests 方法 在构造 request 请求起始url时带上cookies scrapy 不支持 headers 带cookie
import scrapy
class CookieSpider(scrapy.Spider):
name = 'cookie'
# allowed_domains = ['www.com']
start_urls = ['http://www.renren.com/971322510']
cookie_str = "anonymid=jxg4mooq7i6vpy;ln_uact=15614140557;................ "
cookie = {i.split('='): i.split('=') for i in cookie_str.split(';')}
#让cookie 转换为字典
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, cookies=self.cookie, callback=self.parse)
#验证 是否成功
def parse(self, response):
with open('renren.html', 'wb') as f:
f.write(response.body)
2.post 模拟登录
重写父类 start_requests 方法 构造 FormRequest请求起始url时带上form表单 默认是post 登录url
之后需要登录的所有网页即可以访问了 cookie 自动处理
import scrapy
class PostSpider(scrapy.Spider):
name = 'post'
start_urls = ['https://www.renren.com/ajaxLogin/login']
def start_requests(self):
data = {
"email": "15614140557",
"domain": "renren.com",
"icode": "",
"password": "36965a09f50b04d7e4db020b5d21384a8f475581244523435190b4ef13d8fe9a",
}
for url in self.start_urls:
yield scrapy.FormRequest(url=url,formdata=data,callback=self.parse)
def parse(self, response):
3. scrapy 自带的 构造 FormRequest.from_response 对象 传入变化的 如 密码 用户名即可
用xpath自动需找 form表单 表单中需要 有 action 才可以
import scrapy
class AutoSpider(scrapy.Spider):
name = 'auto'
start_urls = ['https://github.com/login']
def parse(self, response):
form_data={
'login':'wyc-12321',
'password':'wyc15213798476'
}
yield scrapy.FormRequest.from_response(
response,
formxpath='//*[@id="login"]/form',
formdata=form_data,
callback=self.parse_detail
)
def parse_detail(self,response):
with open('github.html','wb') as f:
f.write(response.body)
3.scrapy中的 crawl spider
1.正则选择器的 rule
2.注意 crawlspider中 不能重写parse方法 ,这个方法被用来 提取规则解析器中的url
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider,Rule
class AqiproSpider(CrawlSpider):
name = 'aqiPro_Crawl'
# start_urls 默认不过滤 url dont_filter= True
start_urls = ['https://www.aqistudy.cn/historydata/']
rules = (
Rule( LinkExtractor( allow='monthdata' ) ),
Rule( LinkExtractor(allow='daydata'),callback='parse_day'),
Rule(LinkExtractor(restrict_css='#1'), follow=False), 不写callback只负责找url
# css选择器 自动提取 标签下的 a 标签中的 href
)
# crawl url自动过滤
def parse_day(self, response):
yield item
2.rule的其他用法
rule(allow=(),deny=() ,restrict_xpaths() ) deny 不允许提取的url
3.分布式爬虫 Redis spider
1.流程
1.运行一个redisspider 时 发生阻塞,等待主机发布任务,当redis中 有起始url 时 各个分机 开始抢任务,抢到任务的 爬虫文件 继续 提取 url 交给到 调度器,然后 去重后入队列 交给 redis 调度器 添加指纹 后出队列 再返回给 scrapy 调度器
2. 运行
scrapy crawl 项目名 --->阻塞 -->向redis中 lpush redis_key www.baidu.com -->开始爬虫
2.具体文件配置
1.spider文件
import scrapy
from scrapy_redis.spiders import RedisSpider #分布式包
class AqiproSpider(RedisSpider): #更换继承关系
name = 'aqiPro_redis'
redis_key = 'redispider' #主机 分布第一个任务的 key名
def parse(self, response):
pass
2.setting配置
# 1.分布式的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 2.分布式的 过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 3.分布式的 断点续爬
SCHEDULER_PERSIST = True
# 4.分布式的 管道
'scrapy_redis.pipelines.RedisPipeline': 400 #普通scrapy也可以使用
# 5.连接 redis 的 ip port
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
3.rediscrawlspdier
import scrapy
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisCrawlSpider
class AqiproSpider(RedisCrawlSpider): #更换继承父类
name = 'aqiProRedisCrawl'
redis_key = 'RedisCrawl'
rules = (
# 所有城市链接
Rule( LinkExtractor( allow='monthdata' ) ),
#所有 月链接
Rule( LinkExtractor(allow='daydata'),callback='parse_day'),
)
def parse_day(self, response):
yield item
4.redis 持久化存储 原理
普通scrapy 去重原理:将请求url放入 set集合 当结束程序时集合被释放 ,下一次运行时从头开始爬取
redisspider 运行后 redis中 存在一 指纹集合 request 请求url 集合 item字典
1.指纹集合用来存放url的指纹结合(包括处理请求头,参数,cookie)l,越来越来多,程序结束后 继续存在redis中
2.request集合 存放 没有请求过的url redis调度器将这些 url 出队列 交给 scrapy调度器,越来越少
去重原理
def request_seen(self, request):
fp = self.request_fingerprint(request)
# This returns the number of values added, zero if already exists.
added = self.server.sadd(self.key, fp)
return added == 0 #如果 指纹存在 added =0 return True 如果不存在 return False
#fp 就是url cookie header 指纹结合
# 既 添加了指纹 又做了判断
入队列原理
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
# 要过滤为true再去判断指纹 >> 指纹存在返回True >>入队列失败 指纹不存在,返回False>>入队列
#不过滤 直接入队列
self.df.log(request, self.spider)
return False
if self.stats:
#不过滤 入队列 # 过滤 指纹 不存在 入队列
self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider)
self.queue.push(request)
return True