爬虫框架

一. 基本使用

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 爬虫架构

image

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

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/
posted @ 2023-05-12 10:41  codegjj  阅读(58)  评论(0编辑  收藏  举报