scrapy解析数据,配置文件,整站爬取cnblogs,持久化

发布时间 2023-11-12 16:13:19作者: 毓见

1 scrapy解析数据 ?

##### 运行爬虫
scrapy crawl cnblogs

##### 可以项目目录下写个main.py
from scrapy.cmdline import execute
execute(['scrapy','crawl','cnblogs','--nolog'])


#### 重点
1 response对象有css方法和xpath方法
	-css中写css选择器     response.css('')
    -xpath中写xpath选择   response.xpath('')
2 重点1:
	-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 重点2:
	.extract_first()  取一个
    .extract()        取所有
    ##### 使用css选择器解析数据
    def parse(self, response):
        article_list = response.css('article.post-item')
        for article in article_list:
            name = article.css('a.post-item-title::text').extract_first()
            author = article.css('a.post-item-author>span::text').extract_first()
            url = article.css('a.post-item-title::attr(href)').extract_first()
            img = article.css('img.avatar::attr(src)').extract_first()
            desc = article.css('p.post-item-summary::text').extract()  # 文本内容可能放在第二个位置
            desc_content=desc[0].replace('\n', '').replace(' ', '')
            if not desc_content:
                desc_content = desc[1].replace('\n', '').replace(' ', '')

            print('''
            文章标题:%s
            文章作者:%s
            文章地址:%s
            头像:%s
            摘要:%s
            ''' % (name, author, url, img, desc_content))

    #### xpath 解析数据
    def parse(self, response):
        article_list = response.xpath('//article[@class="post-item"]')
        for article in article_list:
            name = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()
            # name = article.xpath('./section/div/a/text()').extract_first()
            author = article.xpath('.//a[@class="post-item-author"]/span/text()').extract_first()
            url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
            img = article.xpath('./section/div/p/a/img/@src').extract_first()
            desc = article.xpath('./section/div/p/text()').extract()  # 文本内容可能放在第二个位置
            desc_content = desc[0].replace('\n', '').replace(' ', '')
            if not desc_content:
                desc_content = desc[1].replace('\n', '').replace(' ', '')

            print('''
            文章标题:%s
            文章作者:%s
            文章地址:%s
            头像:%s
            摘要:%s
            ''' % (name, author, url, img, desc_content))

2 配置文件 ?

#### 基础配置
# 项目名
BOT_NAME = "scrapy_demo"
# 爬虫所在路径
SPIDER_MODULES = ["scrapy_demo.spiders"]
NEWSPIDER_MODULE = "scrapy_demo.spiders"

# 记住  日志级别
LOG_LEVEL='ERROR'


# 请求头中的  USER_AGENT
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

# 是否遵循爬虫协议
ROBOTSTXT_OBEY = False



# 默认请求头
#DEFAULT_REQUEST_HEADERS = {
#    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
#    "Accept-Language": "en",
#}

#爬虫中间件
#SPIDER_MIDDLEWARES = {
#    "scrapy_demo.middlewares.ScrapyDemoSpiderMiddleware": 543,
#}

# 下载中间件
#DOWNLOADER_MIDDLEWARES = {
#    "scrapy_demo.middlewares.ScrapyDemoDownloaderMiddleware": 543,
#}



# 持久化相关
#ITEM_PIPELINES = {
#    "scrapy_demo.pipelines.ScrapyDemoPipeline": 300,
#}



### 高级配置(提高爬取效率)
#1 增加并发:默认16
默认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

3 整站爬取cnblogs--》爬取详情--》数据传递 ?

# 整站爬取:
	爬取所有页
    	-解析出下一页 yield Request(url=next, callback=self.parse)
        
    爬取文章详情
    	-解析出详情地址:yield Request(url=url, callback=self.detail_parser)
        
    多个Request之间数据传递
    	yield Request(url=url,meta={'item':item})
        在解析的 response中 response.meta.get('item')
    def parse(self, response):
        article_list = response.xpath('//article[@class="post-item"]')
        for article in article_list:
            name = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()
            # name = article.xpath('./section/div/a/text()').extract_first()
            author = article.xpath('.//a[@class="post-item-author"]/span/text()').extract_first()
            url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
            img = article.xpath('./section/div/p/a/img/@src').extract_first()
            desc = article.xpath('./section/div/p/text()').extract()  # 文本内容可能放在第二个位置
            desc_content = desc[0].replace('\n', '').replace(' ', '')
            if not desc_content:
                desc_content = desc[1].replace('\n', '').replace(' ', '')

            # print('''
            # 文章标题:%s
            # 文章作者:%s
            # 文章地址:%s
            # 头像:%s
            # 摘要:%s
            # ''' % (name, author, url, img, desc_content))
            # 详情地址:url ----》想继续爬取详情
            item={'name':name,'url':url,'img':img,'text':None}
            yield Request(url=url, callback=self.detail_parser,meta={'item':item})

        #### 继续爬取下一页
        # next='https://www.cnblogs.com'+response.css('div.pager>a:last-child::attr(href)').extract_first()
        next = 'https://www.cnblogs.com' + response.xpath('//div[@class="pager"]/a[last()]/@href').extract_first()
        print(next)
        yield Request(url=next, callback=self.parse)

        # 逻辑---》起始地址:https://www.cnblogs.com---》回到了parse---》自己解析了(打印数据,继续爬取的地址)---》yield Request对象---》第二页---》爬完后又回到parser解析

    def detail_parser(self, response):
        print(len(response.text))
        item=response.meta.get('item')
        text=response.css('#cnblogs_post_body').extract_first()
        item['text']=text
        # 我们想把:上一个请求解析出来的  标题,摘要,图片 和这个请求解析出来的 文本合并到一起
        # 这个text 无法和 上面 parse解析出的文章标题对应上
        print(item)

8 持久化 ?

8.1 基于管道的持久化存储

scrapy框架中已经为我们专门集成好了高效、便捷的持久化操作功能,我们直接使用即可。
要想使用scrapy的持久化操作功能,我们首先来认识如下两个文件:

 items.py:数据结构模板文件。定义数据属性。
    pipelines.py:管道文件。接收数据(items),进行持久化操作。

持久化流程:
    1.爬虫文件爬取到数据后,需要将数据封装到items对象中。
    2.使用yield关键字将items对象提交给pipelines管道进行持久化操作。
    3.在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码将item对象中存储的数据进行持久化存储
    4.settings.py配置文件中开启管道

8.2 小试牛刀:将糗事百科首页中的段子和作者数据爬取下来,然后进行持久化存储

  • 爬虫文件:qiubaiDemo.py
import scrapy
from secondblood.items import SecondbloodItem

class QiubaidemoSpider(scrapy.Spider):
    name = 'qiubaiDemo'
    allowed_domains = ['www.qiushibaike.com']
    start_urls = ['http://www.qiushibaike.com/']

    def parse(self, response):
        odiv = response.xpath('//div[@id="content-left"]/div')
        for div in odiv:
            # xpath函数返回的为列表,列表中存放的数据为Selector类型的数据。我们解析到的内容被封装在了Selector对象中,需要调用extract()函数将解析的内容从Selecor中取出。
            author = div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
            author = author.strip('\n')#过滤空行
            content = div.xpath('.//div[@class="content"]/span/text()').extract_first()
            content = content.strip('\n')#过滤空行

            #将解析到的数据封装至items对象中
            item = SecondbloodItem()
            item['author'] = author
            item['content'] = content

            yield item#提交item到管道文件(pipelines.py)
  • items文件:items.py
import scrapy


class SecondbloodItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field() #存储作者
    content = scrapy.Field() #存储段子内容
  • 管道文件:pipelines.py
class SecondbloodPipeline(object):
    #构造方法
    def __init__(self):
        self.fp = None  #定义一个文件描述符属性
  #下列都是在重写父类的方法:
    #开始爬虫时,执行一次
    def open_spider(self,spider):
        print('爬虫开始')
        self.fp = open('./data.txt', 'w')

   #因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
    def process_item(self, item, spider):
        #将爬虫程序提交的item进行持久化存储
        self.fp.write(item['author'] + ':' + item['content'] + '\n')
        return item

    #结束爬虫时,执行一次
    def close_spider(self,spider):
        self.fp.close()
        print('爬虫结束')
  • 配置文件:settings.py
#开启管道
ITEM_PIPELINES = {
    'secondblood.pipelines.SecondbloodPipeline': 300, #300表示为优先级,值越小优先级越高
}

8.3 基于mysql的管道存储

小试牛刀案例中,在管道文件里将item对象中的数据值存储到了磁盘中,如果将item数据写入mysql数据库的话,只需要将上述案例中的管道文件修改成如下形式:

  • pipelines.py文件
#导入数据库的类
import pymysql
class QiubaiproPipelineByMysql(object):

    conn = None  #mysql的连接对象声明
    cursor = None#mysql游标对象声明
    def open_spider(self,spider):
        print('开始爬虫')
        #链接数据库
        self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='qiubai')
    #编写向数据库中存储数据的相关代码
    def process_item(self, item, spider):
        #1.链接数据库
        #2.执行sql语句
        sql = 'insert into qiubai values("%s","%s")'%(item['author'],item['content'])
        self.cursor = self.conn.cursor()
        #执行事务
        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()

        return item
    def close_spider(self,spider):
        print('爬虫结束')
        self.cursor.close()
        self.conn.close()

  • settings.py
ITEM_PIPELINES = {
    'qiubaiPro.pipelines.QiubaiproPipelineByMysql': 300,
}
  • 面试题:如果最终需要将爬取到的数据值一份存储到磁盘文件,一份存储到数据库中,则应该如何操作scrapy?  

  • 答:管道文件中的代码为

#该类为管道类,该类中的process_item方法是用来实现持久化存储操作的。
class DoublekillPipeline(object):

    def process_item(self, item, spider):
        #持久化操作代码 (方式1:写入磁盘文件)
        return item

#如果想实现另一种形式的持久化操作,则可以再定制一个管道类:
class DoublekillPipeline_db(object):

    def process_item(self, item, spider):
        #持久化操作代码 (方式1:写入数据库)
        return item

在settings.py开启管道操作代码为:

#下列结构为字典,字典中的键值表示的是即将被启用执行的管道文件和其执行的优先级。
ITEM_PIPELINES = {
   'doublekill.pipelines.DoublekillPipeline': 300,
    'doublekill.pipelines.DoublekillPipeline_db': 200,
}

#上述代码中,字典中的两组键值分别表示会执行管道文件中对应的两个管道类中的process_item方法,实现两种不同形式的持久化操作。

总结

基于管道持久化存储的实现流程:
#1.获取解析到的页面数据
#2.在item类中进行相关属性的声明
#3.实例化一个item对象,将解析到的数据值存储到该对象中
#4.将item提交给管道
#5.在管道文件中编写process_item方法(item中的值取出进行持久化存储操作)
#6.在配置文件中开启管道