Scrapy爬虫框架

Scrapy的使用

scrapy: 是一个框架类似于djagno框架在固定的位置写固定的代码即可
基于这个框架写一个爬虫项目

安装
  pip3 install scrapy
  '''
   win上可能装不上(90%都能装上)其实是因为twisted装不上
	 pip3 install wheel  装了它,以后支持直接使用whl文件安装

   安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs

   pip3 install lxml
   pip3 install pyopenssl

   下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
   下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
   执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
   pip3 install scrapy
  '''

# 命令行创建项目
  scrapy startproject myfirst

# 创建爬虫  scrapy genspider  爬虫名  爬虫地址
  scrapy genspider cnblogs www.cnblogs.com

# 运行爬虫
  scrapy crawl cnblogs

Scrapy目录结构

├── first_scrapy         # 项目名字
   ├── items.py         # 模型类写了一些字段,类似于django的models
   ├── middlewares.py   # 中间件:爬虫中间件和下载中间件
   ├── pipelines.py     # 管道:存储数据的代码写在这
   ├── settings.py      # 项目的配置文件
   └── spiders          # 文件夹,下面放了一个个爬虫文件
       └── cnblogs.py   # 一个个的爬虫文件
└── scrapy.cfg           # 项目上线需要用到,不用管

Scrapy架构

# 引擎(EGINE)-->大总管,负责全部的数据流向--》内置的,咱们不需要写

引擎负责控制系统所有组件之间的数据流并在某些动作发生时触发事件

# 调度器(SCHEDULER)---》对要爬取的地址进行排队,去重
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

# 下载器(DOWLOADER)--》真正负责下载---》高效的异步模型
用于下载网页内容, 并将网页内容返回给EGINE下载器是建立在twisted这个高效的异步模型上的

# 爬虫(SPIDERS)--》咱们重点写的地方,解析响应,从响应中提取要保存的数据和下一次爬取的地址
SPIDERS是开发人员自定义的类用来解析responses并且提取items或者发送新的请求

# 项目管道(ITEM PIPLINES)---》存储数据的逻辑---》可以存到文件,redis,mysql。。。
在items被提取后负责处理它们主要包括清理验证持久化比如存到数据库等操作

# 下载器中间件(Downloader Middlewares)--》用的多
位于Scrapy引擎和下载器之间主要用来处理从EGINE传到DOWLOADER的请求request(加请求头加cookie加代理)已经从DOWNLOADER传到EGINE的响应response进行一些处理

# 爬虫中间件(Spider Middlewares)---》用的少
位于EGINE和SPIDERS之间主要工作是处理SPIDERS的输入即responses和输出即requests

img

配置运行爬虫

# first_scrapy/run.py  pycharm中的运行


from scrapy import cmdline

# cmdline.execute(['scrapy', 'crawl' ,'cnblogs','--nolog'])
cmdline.execute(['scrapy', 'crawl', 'cnblogs'])

Scrapy解析数据

1. response对象有css方法和xpath方法
    css中写css选择器
    xpath中写xpath选择

2.
	 xpath取文本内容
		'.//a[contains(@class,"link-title")]/text()'
   xpath取属性
    './/a[contains(@class,"link-title")]/@href'
   css取文本
    'a.link-title::text'
   css取属性
    'img.image-scale::attr(src)'

3.
	 .extract_first()   取一个
   .extract()         取所有

first_scrapy/spiders/cnblogs.py

css
import scrapy

class CnblogsSpider(scrapy.Spider):  # 爬虫类
    name = 'cnblogs'  # 爬虫名
    allowed_domains = ['www.cnblogs.com']  # 允许爬取的域 只爬取该域下的
    start_urls = ['https://www.cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # 解析方法 爬取完后的 response 会到这执行
        # print(response)  # 从 response 解析数据
        # 解析出文章的标题 摘要 作者 作者头像 并继续爬取出文章详情
        # 将爬取的结果组装成一个对象存起来

        # 使用 bs4 解析出需要的数据 scrapy 自带解析库 不需要bs4 支持 css 和 xpath
        # print(response.text)

        article_list = response.css('article.post-item')
        for article in article_list:
            # 获取标题
            title = article.css('section > div > a::text').extract_first()
            # title = article.css('section > div > a::text').extract()[0]  # 和上面相等
            # print(title)

            # 获取摘要
            desc = article.css('section > div > p::text').extract()[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = article.css('section > div > p::text').extract()[1].replace('\n', '').replace(' ', '')
            # print(desc)

            # 作者名称
            author_name = article.css('section > footer > a > span::text').extract_first()
            # print(author_name)

            # 作者头像
            author_img = article.css('section > div > p > a > img::attr(src)').extract_first()
            # print(author_img)

            # 文章详情地址
            article_url = article.css('section > div > a::attr(href)').extract_first()
            # print(article_url)
            print(f'''
                文章标题:{title},
                文章摘要:{desc},
                作者名称:{author_name},
                作者头像:{author_img},
                文章详情:{article_url},
            ''')
xpath
import scrapy

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[@class="post-item"]')
        for article in article_list:
            # 获取标题
            title = article.xpath('./section/div/a/text()').extract_first()
            desc = article.xpath('./section/div/p/text()').extract()[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = article.xpath('./section/div/p/text()').extract()[1].replace('\n', '').replace(' ', '')
            # 作者名称
            author_name = article.xpath('./section/footer/a/span/text()').extract_first()

            # 作者头像
            author_img = article.xpath('./section/div/p/a/img/@src').extract_first()

            # 文章详情地址
            article_url = article.xpath('./section/div/a/@href').extract_first()
            print(f'''
                文章标题:{title},
                文章摘要:{desc},
                作者名称:{author_name},
                作者头像:{author_img},
                文章详情:{article_url},
            ''')

setting配置说明

first_scrapy/settings.py

# 内置一套,用户一套
ROBOTSTXT_OBEY = False     # 是否遵循爬虫协议,如果写了它,一般网站都不让爬,基本写成false
USER_AGENT = '浏览器头'     # 爬虫请求头中USER_AGENT是什么,做成浏览器的样子
LOG_LEVEL='ERROR'          # 日志级别改成ERROR,以后错误日志会打印,普通日志不打印
SPIDER_MIDDLEWARES=[]      # 爬虫中间件,可以写多个
DOWNLOADER_MIDDLEWARES=[]  # 下载中间件类,配置在这,可以配多个
ITEM_PIPELINES=[]          # 保存数据,会执行到的类,类内部写保存逻辑
# 1 增加并发
默认scrapy开启的并发线程为32个可以适当进行增加在settings配置文件中修改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

全站爬取 cnblogs 文章

first_scrapy/spiders/cnblogs.py

import scrapy
from scrapy.http import Request
from ..items import FirstScrapyItem


class CnblogsSpider(scrapy.Spider):  # 爬虫类
    name = 'cnblogs'  # 爬虫名
    allowed_domains = ['www.cnblogs.com']  # 允许爬取的域 只爬取该域下的
    start_urls = ['https://www.cnblogs.com/']  # 起始爬取的地址

    def parse(self, response):  # 解析方法 爬取完后的 response 会到这执行
        article_list = response.css('article.post-item')
        for article in article_list:
            title = article.css('section > div > a::text').extract_first()
            desc = article.css('section > div > p::text').extract()[0].replace('\n', '').replace(' ', '')
            if not desc:
                desc = article.css('section > div > p::text').extract()[1].replace('\n', '').replace(' ', '')
            author_name = article.css('section > footer > a > span::text').extract_first()
            author_img = article.css('section > div > p > a > img::attr(src)').extract_first()
            article_url = article.css('section > div > a::attr(href)').extract_first()

            # 需要一个文章对象 属于items中模型类的对象
            item = FirstScrapyItem()
            # 必须使用 [] 不能使用 .  赋值
            item['title'] = title
            item['desc'] = desc
            item['author_name'] = author_name
            item['author_img'] = author_img
            # 缺文章详情 detail

            # 继续爬取文章详情页 文章详情页的解析方法不能继续使用这个 需要一个单独的解析方法 爬完会触发
            yield Request(url=article_url, callback=self.parse_detail, meta={"item": item})

        # 下一页地址
        next_url = 'https://www.cnblogs.com' + response.css(
            '#paging_block > div > a:last-child::attr(href)').extract_first()

        # next_url = 'https://www.cnblogs.com' + response.xpath(
        #     '//*[@id="paging_block"]/div/a[last()]/@href').extract_first()
        # print(next_url)

        # 继续爬取下一页 再爬完 解析方式 还是这个解析方式
        yield Request(url=next_url)

    def parse_detail(self, response):
        # 解析出文章详情 需要对上之前文章的数据
        item = response.meta.get('item')
        detail = str(response.css('#cnblogs_post_body').extract_first())
        item['detail'] = detail
        yield item  # 触发 pipeline 走存储

first_scrapy/items.py

import scrapy

class FirstScrapyItem(scrapy.Item):
    title = scrapy.Field()
    desc = scrapy.Field()
    author_name = scrapy.Field()
    author_img = scrapy.Field()
    detail = scrapy.Field()

first_scrapy/settings.py

...
ITEM_PIPELINES = {
   'first_scrapy.pipelines.FirstScrapyPipeline': 300,  # 此处可以配多个 数字越小 优先级越高
}
...

first_scrapy/pipelines.py

from itemadapter import ItemAdapter
import pymysql


class FirstScrapyPipeline:
    def open_spider(self, spider):
        print("先打开")
        self.conn = pymysql.connect(
            user='root',
            password="asd123...",
            host='127.0.0.1',
            database='cnblogs',
            port=3306,
            autocommit=True,
        )
        self.curosr = self.conn.cursor()

    def process_item(self, item, spider):
        # 会一次次触发该方法的执行  在这里写保存的逻辑
        print('pipeline: ',item['title'])
				# 存入MySQl数据库中
        self.curosr.execute('insert into article '
                            '(title,`desc`,author_name,author_img,detail) values '
                            '(%s,%s,%s,%s,%s)',
                            args=[item['title'], item['desc'], item['author_name'], item['author_img'], item['detail']])
        return item

    def close_spider(self, spider):
        print("我关闭了")
        self.curosr.close()
        self.conn.close()

下载中间件

# 爬虫和下载中间件要使用 需要在配置文件中配置
SPIDER_MIDDLEWARES = {
  'crawl_cnblogs.middlewares.CrawlCnblogsSpiderMiddleware': 5,
}

# 下载中间件
DOWNLOADER_MIDDLEWARES = {
  'crawl_cnblogs.middlewares.CrawlCnblogsDownloaderMiddleware': 5,
}

first_scrapy/settings.py

...
DOWNLOADER_MIDDLEWARES = {
   'first_scrapy.middlewares.FirstScrapyDownloaderMiddleware': 543,
}
...

first_scrapy/middlewares.py

from scrapy import signals
...

class FirstScrapyDownloaderMiddleware:
    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    # 请求来
    def process_request(self, request, spider):
        # 修改为随机 User-Agent
        from fake_useragent import UserAgent
        ua = UserAgent()
        request.headers["User-Agent"] = ua.random
        print(request.headers[b'User-Agent'])

        # 加入cookie
        # request.cookies = {}

        # 加代理 可以写一个方法 将代理池的代理取出放到这里
        # request.meta['proxy'] = 'http://103.130.172.34:8080'

        return None

    # 请求走
    def process_response(self, request, response, spider):
        return response

...
# fake_useragent模块,可以随机生成user-aget
# pip3 install fake-useragent
	    from fake_useragent import UserAgent
        ua = UserAgent()
        print(ua.ie)   #随机打印ie浏览器任意版本
        print(ua.firefox) #随机打印firefox浏览器任意版本
        print(ua.chrome)  #随机打印chrome浏览器任意版本
        print(ua.random)  #随机打印任意厂家的浏览器