Scrapy爬虫框架阶段案例 网站数据采集

鳄鱼君Ba

发表文章数:518

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python教程 » Scrapy爬虫框架阶段案例 网站数据采集

爬取某网站的文章标题、发布时间、文章分类、文章链接、文章正文(HTML)格式。这里的某网站其实就是鳄鱼君Ba的网站,教程仅用于学习用途,切勿用于商业或非法用途。

网站的结构非常的清晰,所以这里不再对网站进行分析。直接就可以通过Scrapy内建的Xpath来提取相关信息。任务要求:
(1)爬取列表页第1页所有的文章标题和文章详情;
(2)使用MongoDB保存信息;
(3)使用Redis缓存请求;
(4)截取与正文相关的源代码并保存。

项目创建

首先创建Scrapy爬虫项目,命名为eyujun,并在项目中的spiders文件夹里创建创建eyujunSpider.py文件:

scrapy startproject eyujun
cd eyujun
scrapy genspider eyujunSpider xxx.com

项目配置

完成项目创建后,接下来就进入项目开发。按照制定的开发顺序,首先完成项目配置的开发,打开项目的配置文件setting.py,由于文件在创建的时候已有较多的注释代码,因此此处只列出项目所需的代码内容。其代码如下:

# -*- coding: utf-8 -*-
BOT_NAME = 'eyujun'

SPIDER_MODULES = ['eyujun.spiders']
NEWSPIDER_MODULE = 'eyujun.spiders'

# 定义请求头
DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
    "User-Agent":"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
}
# 设置为False
ROBOTSTXT_OBEY = False
LOG_LEVEL='ERROR'
# 配置请求头
HEADERS = {
  'accept': 'text/html, application/xhtml+xml, application/xml; q=0.9, image/webp, image/apng, */*; q=0.8',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'zh-CN, zh; q=0.9, en; q=0.8',
  'cache-control': 'max-age=0',
  'dnt': '1',
  'upgrade-insecure-requests': '1',
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko)Chrome / 67.0.3396.99 Safari / 537.36'
}
# 配置管道
ITEM_PIPELINES = {
   'eyujun.pipelines.EyujunPipeline': 300,
}
REDIS_HOST='127.0.0.1'
REDIS_PORT=6379

分别对Item Pipelines、数据库信息、请求头和文件保存路径进行配置,各个配置说明:

● Item Pipelines:创建项目时,默认配置了类MusicPipeline。在此项目中,需要添加一个下载类DownloadMusicPipeline,该类继承自父类FilesPipeline,主要实现歌曲下载功能。
● 请求头:配置默认的请求头内容,如果项目中发送HTTP请求并没有指定请求头,就默认使用该配置作为请求头。
除此之外,还可以配置并发数和下载延时等相关信息,使用默认配置即可

定义存储字段

在items.py中定义好需要爬取的数据:

# -*- coding: utf-8 -*-
import scrapy
class EyujunItem(scrapy.Item):
    title = scrapy.Field()
    url = scrapy.Field()
    post_time = scrapy.Field()
    category = scrapy.Field()
    detail = scrapy.Field()

定义管道类

我们这里存储到Mongodb数据库,代码参考:

# -*- coding: utf-8 -*-
from pymongo import MongoClient
from scrapy.utils.project import get_project_settings

settings = get_project_settings()
class EyujunPipeline(object):
    def open_spider(self,spider):
        self.client=MongoClient(host='127.0.0.1',port=27017)
        self.database=self.client.blogSpider
        self.collection=self.database.blog
    def process_item(self, item, spider):
        title = item['title']
        url = item['url']
        post_time = item['post_time']
        category = item['category']
        detail = item['detail']
        self.collection.insert_one({'title':title,'url':url,'post_time':post_time,'category':category,'detail':detail})
        return item
    def close_spider(self,spider):
        print('存储到mondodb成功!')

编写爬虫规则

分析网页的源码,采取先抓大再抓小的原则,这里就不做过多分析了,毕竟网页结构非常的清楚,也没有设置任何的反扒措施,所以说非常的简单!

1.提取首页文章的标题和URL

import scrapy,re
from eyujun.items import EyujunItem
class EyujunspiderSpider(scrapy.Spider):
    name = 'eyujunSpider'
    allowed_domains = ['e1yu.com']
    start_urls = ['https://www.e1yu.com']
    def parse(self, response):
        titles_list=response.xpath('//div[@class="excerpt-post"]')
        for titles in titles_list:
            item=EyujunItem()
            item['title']=titles.xpath('./header/h2/a/text()').extract_first()
            item['category']=titles.xpath('./header/a/text()').extract_first()
            item['url']=titles.xpath('./header/h2/a/@href').extract_first()
            item['post_time']=titles.xpath('./p[2]/span[2]/text()').extract_first()
           

代码中的extract_first()方法。“extract_first()”方法的作用是获取XPath提取出来的值中的第一个。“extract()”方法返回的是一个列表,而“extract_first()”返回的就是一个具体的值。因为在每一个a标签中只有一个详情页链接,也只有一个标题,因此使用“extract_first()”比“extract()[0]”更加易懂。

2.请求详情页的URL

在Scrapy中,可以通过scrapy.Request让爬虫爬入一个新的URL,并继续获取新页面的信息。所以,为了让爬虫爬取博客正文页面,可以构造如下的代码:

# -*- coding: utf-8 -*-
import scrapy,re
from eyujun.items import EyujunItem
class EyujunspiderSpider(scrapy.Spider):
    name = 'eyujunSpider'
    allowed_domains = ['e1yu.com']
    start_urls = ['https://www.e1yu.com']
    def parse(self, response):
        titles_list=response.xpath('//div[@class="excerpt-post"]')
        for titles in titles_list:
            item=EyujunItem()
            item['title']=titles.xpath('./header/h2/a/text()').extract_first()
            item['category']=titles.xpath('./header/a/text()').extract_first()
            item['url']=titles.xpath('./header/h2/a/@href').extract_first()
            item['post_time']=titles.xpath('./p[2]/span[2]/text()').extract_first()
            yield scrapy.Request(
                item['url'],
                headers=self.settings['HEADERS'],
                callback=self.parse_detail,
                meta={'item':item})

(1)请求头:headers
参数headers=HEADERS,把请求头添加到Scrapy请求中,使爬虫的请求看起来像是从浏览器发起的。请求头可以放在settings.py配置文件中。
(2)回调函数:callback=self.parse_detail
由于使用到了yield,所以对博客正文页爬取是一个异步的过程。请求会先放在Redis中,等到分布式爬虫中的某一个有空了就去取。这个异步的过程是通过Twisted来实现的。Scrapy不会卡在这个地方等待详情页爬取完成以后再爬取后面的内容,所以这个地方需要使用回调函数callback。详情页的源代码获取成功以后,会被传递给回调函数,然后在回调函数中完成新页面的提取工作。
(3)数据传递:meta={‘item’: item}
meta是一个传递信息的通道。它负责将爬取博文列表页获取到的信息传递给负责爬取正文页的方法中。将item字典从parse方法传递到parse_detail方法中。meta的值是一个字典,里面的名字和值都可以自己设定。
3.获取详情页

self.parse_detail是负责处理详情页并获取信息的一个方法。它负责从整个正文页中正文HTML。代码参考:

def parse_detail(self,response):
    item=response.meta['item']
    content=response.xpath('//article[@class="article-content"]').get()
    item['detail']=re.findall('<div class=""></div>(.*?)<div class="single_tags">',content,re.S)[0].split()
    yield item

由于网页的文章内容需要剔除掉无用的,所以这里使用正则更准确一点,get方法是获取Selector对象的字符串,也就是HTML。最后,把获取到的所有信息保存到item中,并提交给pipelines处理入库。

4.翻页请求URL

翻页可以使用CrawlSpider,也可以手动实现翻页请求。这里通过寻找下一页的URL地址,来实现翻页,代码参考:

def parse(self, response):
    titles_list=response.xpath('//div[@class="excerpt-post"]')
    for titles in titles_list:
        item=EyujunItem()
        item['title']=titles.xpath('./header/h2/a/text()').extract_first()
        item['category']=titles.xpath('./header/a/text()').extract_first()
        item['url']=titles.xpath('./header/h2/a/@href').extract_first()
        item['post_time']=titles.xpath('./p[2]/span[2]/text()').extract_first()
        yield scrapy.Request(
            item['url'],
            headers=self.settings['HEADERS'],
            callback=self.parse_detail,
            meta={'item':item})
        next_page_url = response.xpath('//li[@class="next-page"]/a/@href').extract_first()
        if next_page_url:
            yield scrapy.Request(next_page_url, callback=self.parse,meta={'item':item})

调试运行

这里需要借助scrapy-redis组件实现,分布式,具体的实现方式非常的简单,只需要在原有的代码上做些修改即可,这里可以参考:Scrapy框架结合scrapy-reids组件实现分布式机群爬虫。那么我们首先修改爬虫文件:

# -*- coding: utf-8 -*-
import scrapy,re
from eyujun.items import EyujunItem
from scrapy_redis.spiders import RedisSpider
class EyujunspiderSpider(RedisSpider):
    name = 'eyujunSpider'
    allowed_domains = ['e1yu.com']
    redis_key = 'blogSpider'
    def parse(self, response):
        titles_list=response.xpath('//div[@class="excerpt-post"]')
        for titles in titles_list:
            item=EyujunItem()
            item['title']=titles.xpath('./header/h2/a/text()').extract_first()
            item['category']=titles.xpath('./header/a/text()').extract_first()
            item['url']=titles.xpath('./header/h2/a/@href').extract_first()
            item['post_time']=titles.xpath('./p[2]/span[2]/text()').extract_first()
            yield scrapy.Request(
                item['url'],
                headers=self.settings['HEADERS'],
                callback=self.parse_detail,
                meta={'item':item})
            next_page_url = response.xpath('//li[@class="next-page"]/a/@href').extract_first()
            if next_page_url:
                yield scrapy.Request(next_page_url, callback=self.parse,meta={'item':item})
    def parse_detail(self,response):
        item=response.meta['item']
        content=response.xpath('//article[@class="article-content"]').get()
        item['detail']=''.join(re.findall('<div class=""></div>(.*?)<div class="single_tags">',content,re.S)[0].split())
        yield item

完善配置文件settings.py,代码参考:

#增加了一个去重容器类配置,作用:使用Redis的set集合来存储请求的指纹数据
DUPEFILTER_CLASS='scrapy_redis.dupefilter.RFPDupeFilter'
#使用scrapy-redis组件自己的调度器,指定scheduler队列
SCHEDULER='scrapy_redis.scheduler.Scheduler'
#队列中的内容是否持久保存,为False的时候,关闭redis的同时会清空redis数据
SCHEDULER_PERSIST=True
# 配置管道
ITEM_PIPELINES = {
   'eyujun.pipelines.EyujunPipeline': 300,
  'scrapy_redis.pipelines.RedisPipeline':400,
}
REDIS_HOST='127.0.0.1'
REDIS_PORT=6379

接下来运行这个爬虫,首先需要打开Redis-ServerMongoDB。 爬虫会卡在这里,知道redis_key中出现第一个URL为止。使用了RedisSpider作为爬虫的父类以后,爬虫会直接监控Redis中的数据,并不读取start_urls中的数据。Redis现在是空的,所以爬虫处于等待状态。

通过redis-cli手动将初始的URL放到Redis中:

lpush blogSpider https://www.e1yu.com/

把网址放到Redis中,爬虫这边就有数据滚动了,可以看到正常爬取到了网站的数据。

查看Redis中的Key,可以看到有一个“1) “BlogSpider:dupefilter””,这里面保存的就是已经爬取过的网址。Scrapy每一次发起请求之前都会在这里检查网址是否重复。因此如果确实需要再一次爬取数据,在Redis中把这个Key删除即可。

未经允许不得转载:作者:鳄鱼君Ba, 转载或复制请以 超链接形式 并注明出处 鳄鱼君Ba
原文地址:《Scrapy爬虫框架阶段案例 网站数据采集》 发布于2020-06-17

分享到:
赞(0) 赏杯咖啡

评论 抢沙发

7 + 2 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册