scrapy框架的运行流程以及数据传递过程:
引擎(engine):负责数据和信号在不腰痛模块间的传递
调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象
下载器(downloader):发送引擎发过来的request请求,获取响应,并将响应交给引擎
爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎
管道(pipeline):处理引擎传递过来的数据,比如存储
下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip
爬虫中间件(spider middleware):可以自定义request请求和进行response过滤,与下载中间件作用重复
解析并获取scrapy爬虫中的数据:
1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
2. extract() 返回一个包含有字符串的列表,配合[0]使用
3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
4. get() 提取列表中第1个文本内容(等同于extract_first())
我们知道可以通过scrapy.Request()指定method、body参数来发送post请求;但是通常使用scrapy.FormRequest()来发送post请求
注意:scrapy.FormRequest()能够发送表单和ajax请求,参考阅读 https://www.cdsy.xyz/computer/programme/Python/230430/cd43347.html
response.url:当前响应的url地址
1. response.request.url:当前响应对应的请求的url地址
2. response.headers:响应头
3. response.requests.headers:当前响应的请求头
4. response.body:响应体,也就是html代码,byte类型
5. response.status:响应状态码
1.在settings.py中通过设置COOKIES_DEBUG=TRUE 能够在终端看到cookie的传递传递过程
2.如果start_url地址中的url是需要登录后才能访问的url地址,则需要重写3.start_request方法并在其中手动添加上cookie
4.如果重新start_requests()方法,则需要去掉start_urls变量
在爬虫文件的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"]
注意
1. meta参数是一个字典
2. meta字典中有一个固定的键proxy,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件
管道的基本使用:
1. 完善pipelines.py中的process_item函数
2. 在settings.py中设置开启pipeline
3. pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过:权重值小的优先执行
4. 1. 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
5. pipeline中process_item的方法必须有,否则item没有办法接受和处理
6. process_item方法接受item和spider,其中spider表示当前传递item过来的spider
在settings.py设置开启pipeline
ITEM_PIPELINES = {
'myspider.pipelines.ItcastFilePipeline': 400, # 400表示权重
'myspider.pipelines.ItcastMongoPipeline': 500, # 权重值越小,越优先执行!
}
1. 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分
# 【1】settings.py中添加
ITEM_PIPELINES = {
# 添加MongoDB管道
'Tencent.pipelines.TencentMongoPipeline': 400,
}
MONGO_HOST = '127.0.0.1'
MONGO_PORT = 27017
MONGO_DB = 'tencentdb'
MONGO_SET = 'tencentset'
# 【2】pipelines.py中新建MongoDB管道类
from .settings import *
import pymongo
class TencentMongoPipeline:
def open_spider(self, spider):
self.conn = pymongo.MongoClient(MONGO_HOST, MONGO_PORT)
self.db = self.conn[MONGO_DB]
self.myset = self.db[MONGO_SET]
def process_item(self, item, spider):
self.myset.insert_one(dict(item))
import json
class WangyiPipeline(object):
def open_spider(self, spider):
if spider.name == 'job':
self.file = open('wangyi.json', 'w')
def process_item(self, item, spider):
if spider.name == 'job':
item = dict(item)
str_data = json.dumps(item, ensure_ascii=False) + ',\n'
self.file.write(str_data)
return item
def close_spider(self, spider):
if spider.name == 'job':
self.file.close()
1)MySQL建表
create database tencentdb charset utf8;
use tencentdb;
create table tencenttab(
job_name varchar(200),
job_type varchar(200),
job_duty varchar(2000),
job_require varchar(2000),
job_add varchar(100),
job_time varchar(100)
)charset=utf8;
2)MySql数据持久化
# 【1】settings.py添加
ITEM_PIPELINES = {
# 在原来基础上添加MySQL的管道
'Tencent.pipelines.TencentMysqlPipeline': 200,
}
MYSQL_HOST = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PWD = '123456'
MYSQL_DB = 'tencentdb'
CHARSET = 'utf8'
# 【2】pipelines.py新建MySQL管道类
from .settings import *
import pymysql
class TencentMysqlPipeline:
def open_spider(self, spider):
self.db = pymysql.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PWD, MYSQL_DB, charset=CHARSET)
self.cur = self.db.cursor()
self.ins = 'insert into tencenttab values(%s,%s,%s,%s,%s,%s)'
def process_item(self, item, spider):
li = [
item['job_name'],
item['job_type'],
item['job_duty'],
item['job_require'],
item['job_add'],
item['job_time'],
]
self.cur.execute(self.ins, li)
self.db.commit()
return item
def close_spider(self, item, spider):
self.cur.close()
self.db.close()
import csv
class LianjiaspiderPipeline(object):
def open_spider(self, spider):
if spider.name == "home":
self.file = open('lianjia.csv', 'w', encoding="utf-8")
self.f_csv = csv.writer(self.file)
headers = ['地址', '总价', '单价', '户型', '面积', "朝向", "楼层", "装修程度", "详情页url"]
self.f_csv.writerow(headers)
def process_item(self, item, spider):
if spider.name == "home":
data = [item["address"], item["price"], item["unit_price"], item["door_model"], item["area"],
item["direction"], item["floor"], item["decorate"], item["info_url"]]
self.f_csv.writerow(data)
print(data)
return item
def close_spider(self, spider):
if spider.name == "home":
self.file.close()
# 管道
ITEM_PIPELINES = {
'LianJiaSpider.pipelines.LianjiaspiderPipeline': 300,
}
1.下载器中间件
2.爬虫中间件
1. 对header以及cookie进行更换和处理
2. 使用代理ip等
3. 对请求进行定制化操作,
但在scrapy默认的情况下 两种中间件都在middlewares.py一个文件中
爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件
from fake_useragent import UserAgent
class RandomUserAgentMiddleware(object):
def process_request(self, request, spider):
ua = UserAgent()
request.headers['User-Agent'] = ua.random
DOWNLOADER_MIDDLEWARES = {
'xxx项目名.middlewares.RandomUserAgentMiddleware': 300,
}
注意:UserAgent中间件设置为None,这样就不会启用,否则默认系统的这个中间会被启用。
DOWNLOADER_MIDDLEWARES = {
'项目名.middlewares.RandomProxy': 543,
}
PROXY_LIST =[
{"ip_port": "123.207.53.84:16816", "user_passwd": "morganna_mode_g:ggc22qxp"},
# {"ip_port": "122.234.206.43:9000"},
]
from 项目名.settings import PROXY_LIST
class RandomProxy(object):
def process_request(self, request, spider):
proxy = random.choice(PROXY_LIST)
print(proxy)
if 'user_passwd' in proxy:
# 对账号密码进行编码,python3中base64编码的数据必须是bytes类型,所以需要encode
b64_up = base64.b64encode(proxy['user_passwd'].encode())
# 设置认证
request.headers['Proxy-Authorization'] = 'Basic ' + b64_up.decode()
# 设置代理
request.meta['proxy'] = proxy['ip_port']
else:
# 设置代理
request.meta['proxy'] = proxy['ip_port']
收费代理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
DOWNLOADER_MIDDLEWARES = {
'myspider.middlewares.ProxyMiddleware': 543,
}
# scrapy-redis分布式配置 pip install scrapy-redis
# 重新指定调度器: 启用Redis调度存储请求队列!!!
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 重新指定去重机制: 确保所有的爬虫通过Redis去重!!!
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# redis配置
REDIS_HOST = '127.0.0.1' # ip
REDIS_PORT = 6379 # 端口
# REDIS_PARAMS = {
# 'password': '', # 密码
# }
# 布隆过滤器配置 pip install scrapy-redis-bloomfilter
# 重新指定调度器
SCHEDULER = "scrapy_redis_bloomfilter.scheduler.Scheduler"
# 重新指定去重机制
DUPEFILTER_CLASS = "scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter"
# 使用哈希函数的数量,默认为6
BLOOMFILTER_HASH_NUMBER = 6
# 使用Bloomfilter的Redis内存,30表示2^30 = 128MB,默认为30
BLOOMFILTER_BIT = 30
设置数据导出编码(主要针对于json文件)
FEED_EXPORT_ENCODING = 'utf-8'
设置User-Agent
USER_AGENT = ''
设置最大并发数(默认为16)
CONCURRENT_REQUESTS = 32
下载延迟时间(每隔多长时间请求一个网页)
DOWNLOAD_DELAY = 0.5
请求头
DEFAULT_REQUEST_HEADERS = {'Cookie' : 'xxx'}
添加项目管道
ITEM_PIPELINES = {'目录名.pipelines.类名' : 优先级}
COOKIES_ENABLED = False
from scrapy import cmdline
cmdline.execute("scrapy crawl hot".split())
# cmdline.execute("scrapy crawl hot -o hot.csv".split()) # 导出csv
# cmdline.execute("scrapy crawl hot -o hot.json".split()) # 导出json
# 注意: settings.py中设置导出编码 - 主要针对json文件
FEED_EXPORT_ENCODING = 'utf-8'
Excel打开csv时出现乱码
- 解决办法:用记事本打开CSV,文件菜单中另存为UTF-8保存
from scrapy.cmdline import execute
execute("scrapy crawl 爬虫文件名1".split())
execute("scrapy crawl 爬虫文件名2".split())
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
crawler = CrawlerProcess(settings)
crawler.crawl('爬虫文件名1')
crawler.crawl('爬虫文件名2')
crawler.start()
crawler.start()