您当前的位置:首页 > 计算机 > 编程开发 > Python

Python爬虫Scrapy轮子工具

时间:04-30来源:作者:点击数:

概念

scrapy框架的运行流程以及数据传递过程:

  1. 爬虫中起始的url构造成request对象–>爬虫中间件–>引擎–>调度器
  2. 调度器把request–>引擎–>下载中间件—>下载器
  3. 下载器发送请求,获取response响应---->下载中间件---->引擎—>爬虫中间件—>爬虫
  4. 爬虫提取url地址,组装成request对象---->爬虫中间件—>引擎—>调度器,重复步骤2
  5. 爬虫提取数据—>引擎—>管道处理和保存数据

模块作用

引擎(engine):负责数据和信号在不腰痛模块间的传递

调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象

下载器(downloader):发送引擎发过来的request请求,获取响应,并将响应交给引擎

爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎

管道(pipeline):处理引擎传递过来的数据,比如存储

下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip

爬虫中间件(spider middleware):可以自定义request请求和进行response过滤,与下载中间件作用重复

开发步骤

  1. 创建项目:
    scrapy startproject mySpider
    scrapy startproject <项目名字>
  2. 生成一个爬虫【在项目目录下执行】:
    scrapy genspider lianjia lianjia.com
    scrapy genspider <爬虫名字> <允许爬取的域名>
    爬虫名字: 作为爬虫运行时的参数
    允许爬取的域名: 为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的url,如果爬取的url与允许的域不通则被过滤掉。
  3. 提取数据:
    修改start_urls
    检查修改allowed_domains
    编写解析方法
    根据网站结构在spider中实现数据采集相关内容
  4. 保存数据:
    在pipelines.py文件中定义对数据处理的管道
    在settings.py文件中注册启用管道
  5. 启动项目

爬虫文件 spiders.py

代码流程

  1. 完善并使用Item数据类:
  2. 在items.py中完善要爬取的字段
  3. 在爬虫文件中先导入Item
  4. 实力化Item对象后,像字典一样直接使用
  5. 构造Request对象,并发送请求:
  6. 导入scrapy.Request类
  7. 在解析函数中提取url
  8. yield scrapy.Request(url, callback=self.parse_detail, meta={})
  9. 利用meta参数在不同的解析函数中传递数据:
  10. 通过前一个解析函数 yield scrapy.Request(url, callback=self.xxx, meta={}) 来传递meta
  11. 在self.xxx函数中 response.meta.get(‘key’, ‘’) 或 response.meta[‘key’] 的方式取出传递的数据

提取数据

解析并获取scrapy爬虫中的数据:
1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
2. extract() 返回一个包含有字符串的列表,配合[0]使用
3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
4. get()  提取列表中第1个文本内容(等同于extract_first())

scrapy.Request发送post请求

我们知道可以通过scrapy.Request()指定method、body参数来发送post请求;但是通常使用scrapy.FormRequest()来发送post请求

发送post请求

注意:scrapy.FormRequest()能够发送表单和ajax请求,参考阅读 https://www.cdsy.xyz/computer/programme/Python/230430/cd43347.html

response响应

response.url:当前响应的url地址

1. response.request.url:当前响应对应的请求的url地址
2. response.headers:响应头
3. response.requests.headers:当前响应的请求头
4. response.body:响应体,也就是html代码,byte类型
5. response.status:响应状态码

Cookie

1.在settings.py中通过设置COOKIES_DEBUG=TRUE 能够在终端看到cookie的传递传递过程
2.如果start_url地址中的url是需要登录后才能访问的url地址,则需要重写3.start_request方法并在其中手动添加上cookie
4.如果重新start_requests()方法,则需要去掉start_urls变量

参数解释

  1. 中括号里的参数为可选参数
  2. callback:表示当前的url的响应交给哪个函数去处理
  3. meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等
  4. dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动
  5. method:指定POST或GET请求
  6. headers:接收一个字典,其中不包括cookies
  7. cookies:接收一个字典,专门放置cookies
  8. body:接收json字符串,为POST的数据,发送payload_post请求时使用(在下一章节中会介绍post请求)
  • 注意:解析函数中的yield能够传递的对象只能是:BaseItem, Request, dict, None

meta参数的使用

  • 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"]

注意

1. meta参数是一个字典

2. meta字典中有一个固定的键proxy,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件

管道 piplines.py

管道的基本使用:

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

pipeline中常用的方法:

  1. process_item(self,item,spider):
    • 管道类中必须有的函数
    • 实现对item数据的处理
    • 必须return item
  2. open_spider(self, spider): 在爬虫开启的时候仅执行一次
  3. close_spider(self, spider): 在爬虫关闭的时候仅执行一次

开启管道

在settings.py设置开启pipeline
ITEM_PIPELINES = {
    'myspider.pipelines.ItcastFilePipeline': 400, # 400表示权重
    'myspider.pipelines.ItcastMongoPipeline': 500, # 权重值越小,越优先执行!
}

多个管道的开启作用

1. 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分

MongoDB 持久化

# 【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))

Json 持久化

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()

MySql 持久化

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()
  • 进阶:mysql连接池

Csv持久化

  • piplines.py
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()
  • settings.py
# 管道
ITEM_PIPELINES = {
    'LianJiaSpider.pipelines.LianjiaspiderPipeline': 300,
}

中间件 middleware.py

中间件的分类

1.下载器中间件
2.爬虫中间件

Scrapy中间的作用(预处理Request和Response对象)

1. 对header以及cookie进行更换和处理
2. 使用代理ip等
3. 对请求进行定制化操作,

但在scrapy默认的情况下 两种中间件都在middlewares.py一个文件中

爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件

Downloader Middlewares默认的方法:

  • process_request(self, request, spider):
    1. 当每个request通过下载中间件时,该方法被调用。
      2. 返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法
      3. 返回Response对象:不再请求,把response返回给引擎
      4. 返回Request对象:把request对象通过引擎交给调度器,此时将不通过其他权重低的process_request方法
  • process_response(self, request, response, spider):
    1. 当下载器完成http请求,传递响应给引擎的时候调用
      2. 返回Resposne:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的process_response方法
      3. 返回Request对象:通过引擎交给调取器继续请求,此时将不通过其他权重低的process_request方法
  • 在settings.py中配置开启中间件,权重值越小越优先执行

fake_useragent

  • middlewares.py
from fake_useragent import UserAgent
class RandomUserAgentMiddleware(object):
    def process_request(self, request, spider):
        ua = UserAgent()
        request.headers['User-Agent'] = ua.random
  • settings.py
DOWNLOADER_MIDDLEWARES = {
    'xxx项目名.middlewares.RandomUserAgentMiddleware': 300,
}

注意:UserAgent中间件设置为None,这样就不会启用,否则默认系统的这个中间会被启用。

ip_proxy

方法一【免费代理】:
  • settings.py
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"},
]
  • middlewares.py
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是否可用

在使用了代理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
DOWNLOADER_MIDDLEWARES = {
'myspider.middlewares.ProxyMiddleware': 543,
}

Scrapy-redis

  • settings.py 文件配置[注意:scrapy-redis 与 bloomfilter 二者只能选其一使用]
# 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': '',  # 密码
# }

Scrapy-bloomfilter布隆过滤器

  • settings.py文件配置
# 布隆过滤器配置 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

设置 settings.py

常用变量

设置数据导出编码(主要针对于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	

启动运行

方法一【运行单个爬虫文件】:

  • 在根目录下创建start.py
    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保存

方法二【 串行 运行完第一个才会运行下一个】:

  • 在根目录下创建main.py
    from scrapy.cmdline import execute
    
    execute("scrapy crawl 爬虫文件名1".split()) 
    execute("scrapy crawl 爬虫文件名2".split()) 
    

方法三【在进程里面跑多个爬虫】:

  • 在根目录下创建main.py
    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()
    
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门