Scrapy爬取伯乐在线所有文章和图片并提取有用的数据

1.首先是环境的搭建

首先我们的环境是再python3.6环境下搭建的,但是由于scrapy依赖的包过多所以我推荐大家下载使用annconda这个集群环境!这个环境是异常强大的,它会使我们安装scrapy变的非常的简单!

我们可以先使用(这里使用虚拟环境进行项目的管理)

conda install virtualenv

然后我们(这个是管理虚拟环境的)

pip install virtualenvwrapper-win

virtualenv 能够通过根据不同的 python 版本创建对应不同版本的虚拟环境,virtualwrapper 能够方便的在不同的虚拟环境之间进行切换。

进入的命令是

workon 虚拟环境名字

退出虚拟环境是 

deactivate

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第1张图片

新建名为py3的虚拟环境

2.scrapy的安装

再虚拟环境下

pip install scrapy

不过我们这里推荐使用豆瓣语言的下载方式因为scrapy依赖的包过多,如果网速不好上面的安装就可能会报一个timeout的错误

pip install -i https://pypi.douban.com/simple scrapy

这样我们就是用了豆瓣提供的镜像进行下载了,速度会非常的快!


安装完成后检验一下是否安装成功!


成功!!!!

3.然后我们新建我们的工程

这里我们终端里进入我们的D:\Python_test下创建工程(注意这里我们要再我们的虚拟环境之下!!!)

scrapy startproject Spider(工程名字)

打开pycharm导入我们的工程!

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第2张图片

然后ok选择open in new window 新窗口打开这个工程

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第3张图片

进入后 是scrapy为我们准备的模板然后我们终端里面建立我们的jobbole.py的模板文件

然后pycharm里面会多出来一个jobbole.py

然后scrapy为我们提供了一个相当方便的用于调试工程的方法:我们再Spider的根目录新建一个main文件用于工程的调试

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第4张图片

创建好之后我们需要自己进行调试代码的书写:

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第5张图片

这是scrapy为我们提供的简单的调试的方法我们可以再终端里面试一试


终端里面启动这个名字叫做jobbole的spider(爬虫)

如果我们的系统是windos系统首次执行可能会报错。那是因为我们windows缺少一个包名字叫做pypiwin32的包


再次运行即可启动

4.对数据的提取

这里我们会采用两种方式对页面的数据进行分析和提取

1.我们采用scrapy提供的response.xpath这种selecter选择器进行提取

'''title = 标题! create_date! = 发布日期! praise_nums = 点赞数! fav_nums = 收藏数! comment_nums = 评论数! content=原文!  tags = 文章类型  由于下面的tag_list
        中的评论数重复了所以采用了去重!!''

     
 
  
# -*- coding: utf-8 -*-
import re
import scrapy
import datetime
from scrapy.http import Request
from urllib import parse
from Spider.items import JoblleArticleItem
from Spider.utils.common import get_md5
class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/103845/']

    def parse(self,response):
        ###实例化items中的类
        article_item = JoblleArticleItem()
        '''title = 标题! create_date! = 发布日期! praise_nums = 点赞数! fav_nums = 收藏数! comment_nums = 评论数! content=原文!  tags = 文章类型  由于下面的tag_list
                中的评论数重复了所以采用了去重!!'''
        front_image_url = response.meta.get("front_image_url","")  #文章封面图
        title= response.xpath("//div[@class='entry-header']/h1/text()").extract_first().strip()
        create_date = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract_first().strip().replace('·','').strip()
        praise_nums = str(response.xpath("//span[contains(@class,'vote-post-up')]/h10/text()").extract_first())
        match_re1 = re.match('.*?(\d+).*?',praise_nums)
        if match_re1:
            praise_nums = int(match_re1.group(1))
        else:
            praise_nums = 0
        fav_nums = str(response.xpath("//span[contains(@class,'bookmark-btn')]/text()").extract_first())
        match_re2 = re.match('.*?(\d+).*?',fav_nums)
        if match_re2:
            fav_nums = int(match_re2.group(1))
        else:
            fav_nums = 0
        comment_nums = str(response.xpath("//a[@href='#article-comment']/span/text()").extract_first())
        match_re3 = re.match('.*?(\d+).*?',comment_nums)
        if match_re3:
            comment_nums = int(match_re3.group(1))
        else:
            comment_nums = 0
        content = response.xpath("//div[@class='entry']").extract_first()
        tag_list = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
        tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
        tags = ",".join(tag_list)
        psss
 
  

我们调试resposne.xpath语句的时候可以再终端里用scrapy为我们提供的scrapy  shell 进行调试

会返回我们需要的数据,我们可以用这种方法进行对数据提取的语句的检查!!!

这样我们就完成了单个页面的数据的提取但是我们这里的需求是这个网址的所有的数据所以我们应该回过头观看网页的源码观察文章列表的下一页的网页源代码。

这个时候我们会发现所有文章的网址是http://blog.jobbole.com/all-posts/ 

这个时候我们jobbole.py里面的starts_urls 就需要更改为这个首列表页。而列表页中单个文章的url我们通过网页源代码可以看出它们都在div class = “post floated-thumb”下的div class=“post-thumb”下的a标签的href标签里

这个时候我们应该思考如何从这个列表页中爬出所有的文章的url并访问提取值然后再提取下一个列表页直到列表页中下一页这个标签消失为空这时候我们就需要修改我们的jobbole.py文件我们可以使用scrapy为我们提供的回调函数的方法完成这个逻辑!!我们为了使代码清晰获取访问链接的就放到parse()函数中,我们自定义一个parse_data()函数用于数据的提取。完成所有网页的爬取的具体代码如下:

def parse(self, response):
    '''
    1.获取文章列表页面中的文章url并交给scrapy下载后并进行解析
    当我们获取到的href的url为不完整的时候可以使用
    2.获取下一页的url并交给scrapy进行下载,下载完成后交给parse
    '''
    #获取一页当中的所有url并交给scrapy进行解析
    #当我们获取到的href的url为不完整的时候 一般response.url+post_url 拼接完整url地址也可使用官方模块
    #from urllib import parse
    # 然后Request(url = parse.urljoin(response.url,post_url),callback=self.parse_data)  此处完整就不使用了
    post_nodes = response.xpath("//div[@class='post floated-thumb']/div[@class='post-thumb']/a")
    for post_node in post_nodes:
        image_url = post_node.xpath("./img/@src").extract_first("")
        post_url =  post_node.xpath("./@href").extract_first("")
        yield Request(url=parse.urljoin(response.url,post_url),meta={
   "front_image_url":image_url},callback=self.parse_data)  #callback为回掉函数此处回掉自己写的parse_data函数对每个网页里面的数据进行解析
    #提取下一页并交给scrapy进行下载
    next_url = response.xpath("//a[@class='next page-numbers']/@href").extract_first()
    if next_url:
        yield Request(url=parse.urljoin(response.url,next_url),callback=self.parse)

这里回调函数的用法就是我们上面调用的 from scrapy.http import Request 这个模板的用法我们可以把

parse_data()函数作callback的值即

yield Request(url=parse.urljoin(response.url, post_url),callback=self.parse_detail)

这一句中的小知识:

(1)yield 的使用:一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator

看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())

才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭

代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield

中断了数次,每次中断都会通过 yield 返回当前的迭代值。 好处就是:把一个函数改写为一个 generator

就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程

异常清晰。

(2)from urllib import parse 模块的使用有的时候我们提取网页源代码的时候会发现我们提取出来的链接

是/113834/这种不完整的链接我们就需要进行url的拼接,但是这个库可以完美解决这种问题,一般这种

情况就是把链接域名省略了,但是域名就是本网页的域名,这时候parse.urljoin(response.url, post_url)

就会把本网页的域名和解析出来得到的不完整的域名进行拼接形成完整的域名http://blog.jobbole.com/113834/

这种形式的完整的有效的url地址。

import re
import scrapy
from scrapy.http import Request
from urllib import parse
 
  
class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/']
    def parse(self, response):
        '''
        1.获取文章列表页面中的文章url并交给scrapy下载后并进行解析
        当我们获取到的href的url为不完整的时候可以使用
        2.获取下一页的url并交给scrapy进行下载,下载完成后交给parse
        '''
        #获取一页当中的所有url并交给scrapy进行解析
        #当我们获取到的href的url为不完整的时候 一般response.url+post_url 拼接完整url地址也可使用官方模块
        #from urllib import parse
        # 然后Request(url = parse.urljoin(response.url,post_url),callback=self.parse_data)  此处完整就不使用了
        post_nodes = response.xpath("//div[@class='post floated-thumb']/div[@class='post-thumb']/a")
        for post_node in post_nodes:
            image_url = post_node.xpath("./img/@src").extract_first("")
            post_url =  post_node.xpath("./@href").extract_first("")
            yield Request(url=parse.urljoin(response.url,post_url),meta={
   "front_image_url":image_url},callback=self.parse_data)  #callback为回掉函数此处回掉自己写的parse_data函数对每个网页里面的数据进行解析
        #提取下一页并交给scrapy进行下载
        next_url = response.xpath("//a[@class='next page-numbers']/@href").extract_first()
        if next_url:
            yield Request(url=parse.urljoin(response.url,next_url),callback=self.parse)

    def parse_data(self,response):
        '''title = 标题! create_date! = 发布日期! praise_nums = 点赞数! fav_nums = 收藏数! comment_nums = 评论数! content=原文!  tags = 文章类型  由于下面的tag_list
                中的评论数重复了所以采用了去重!!'''
        front_image_url = response.meta.get("front_image_url","")  #文章封面图
        title= response.xpath("//div[@class='entry-header']/h1/text()").extract_first().strip()
        create_date = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract_first().strip().replace('·','').strip()
        praise_nums = str(response.xpath("//span[contains(@class,'vote-post-up')]/h10/text()").extract_first())
        match_re1 = re.match('.*?(\d+).*?',praise_nums)
        if match_re1:
            praise_nums = int(match_re1.group(1))
        else:
            praise_nums = 0
        fav_nums = str(response.xpath("//span[contains(@class,'bookmark-btn')]/text()").extract_first())
        match_re2 = re.match('.*?(\d+).*?',fav_nums)
        if match_re2:
            fav_nums = int(match_re2.group(1))
        else:
            fav_nums = 0
        comment_nums = str(response.xpath("//a[@href='#article-comment']/span/text()").extract_first())
        match_re3 = re.match('.*?(\d+).*?',comment_nums)
        if match_re3:
            comment_nums = int(match_re3.group(1))
        else:
            comment_nums = 0
        content = response.xpath("//div[@class='entry']").extract_first()
        tag_list = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
        tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
        tags = ",".join(tag_list)

5.items.py的使用

parse 函数提取数据之后,生成 item,scrapy 会通过 http 将 item 传到 pipeline 进行处理,那么这一小节,我们使用 item 来接收 parse 提取的数据。在 items.py 文件中,定义一个我们自己的数据类 JoblleArticleItem,并继承 scrapy.item 类

class JoblleArticleItem(scrapy.Item):
    title = scrapy.Field()        #Field()能够接收和传递任何类型的值,类似于字典的形式
    url = scrapy.Field()          #获取的文章的url
    url_object_id = scrapy.Field()#文章url的ID我们后面会使用
    front_image_url = scrapy.Field() #文章的首个图面的URL
    front_image_path = scrapy.Field()#图片的保存路径
    create_date = scrapy.Field()     #发布日期
    praise_nums = scrapy.Field()     #点赞数
    fav_nums = scrapy.Field()        #收藏数
    comment_nums = scrapy.Field()    #评论数
    content = scrapy.Field()         #文章原文
    tags = scrapy.Field()            #文章标签

Field() 对象,能够接收和传递任何类型的值,看源代码,就能发现,Field() 类继承自 dict 对象,具有字典的所有属性。

在上面定义的类中,我们增加了一个新的成员变量url_object_id和front_image_url,这两个参数保存的分别是文章的id和文章图片

链接。我们需要将图片下载保存到本地环境中。既然使用了item接收我们提取的数据,那么jobbole.py就需要改动parse_data函数!

我们改动的时候需要先在jobbole.py中导入我们自己定义的JoblleArticleItem类:

from Spider.items import JoblleArticleItem

然后我们再prase_data()函数中实例化我们的类:

article_item = JoblleArticleItem()

然后就是赋值item对象

article_item["url_object_id"] = get_md5(response.url)
article_item["title"] = title
article_item["url"] = response.url
article_item["front_image_url"] = [front_image_url]
try:
    create_date = datetime.datetime.strptime(create_date,'%Y/%m/%d').date()
except Exception as e:
    create_date = datetime.datetime.now().date()
article_item["create_date"] = create_date
article_item["praise_nums"] = praise_nums
article_item["fav_nums"] = fav_nums
article_item["comment_nums"] = comment_nums
article_item["content"] =content
article_item["tags"] = tags
解释一下上面代码中的 front-img-url,这个是在 parse 函数中作为参数 meta 传递给 Request() 函数,回调函数调用 parse_detail,
返回的 response 对象中的 meta 成员,将包含这个元素, meta 就是一个字典, response.meta.get(“front-image-url”) 
将获取到我们传递过来的图片url

这里还需要说明这个create_data类型应当是日期类型采用datetime的datetime.datetime.strptime(creat_date,'%Y/%m/%d').date()的方法进行日期格式化输出还有就是日期为空的时候的异常处理机制

这里,parse 函数成功生成了我们定义的 item 对象,将数据传递到 pipeline。那么,图片链接已经获取到了,我们如下将图片下载下来呢。

scrapy 提供了一个 ImagesPipeline 类,可直接用于图片操作,只需要我们在 settings.py 文件中进行配置即可。

ITEM_PIPELINES = {

'scrapy.pipelines.images.ImagesPipeline': 1,

}

同时,还需要在 settings.py 中配置,item 中哪一个字段是图片 url,以及图片需要存放什么位置

IMAGES_URLS_FIELD = "front_image_url"       #item中的图片url,用于下载
project_dir = os.path.abspath(os.path.dirname(__file__))   #保存到当前文件所在目录
IMAGES_STORE = os.path.join(project_dir,"images")         #下载图片的保存位置

这些参数,可以在 ImagesPipeline 类的源代码中查看到

注意:上面配置好后,上面的代码是在工程路径下面创建一个 images 的目录,用于保存图片,运行 main.py,可能会出现如下错误:

no module named PIL,这是因为图片操作需要 pillow 库,只需要安装即可 

pip install pillow,快速安装,就按照我上面说的豆瓣源的方法。

还可能出现”ValueError: Missing scheme in request url: h”的错误,这是因为图片操作,要求 front_img_url 的值为 list 或者可以迭代

的对象,所以我们在 parse _data函数中给 item 赋值的时候, front_img_url 就是赋值的 list 对象

    

将数据导出到 json 文件中

好了,既然已经将数据通过 ItemLoader 获取到了,那么我们现在就将数据从 pipeline 输出到 json 文件中。

将数据以 json 格式输出,可以通过 json 库的方法,也可以使用 scrapy.exporters 的方法。

import codecs
import json
from scrapy.exporters import JsonItemExporter

然后写自定义导入到本地json文件的类

#####自定义导出到本地json文件
class JsonWithEncodingPipeline(object):
    def __init__(self):
        self.file = codecs.open("article.json",'w',encoding='utf-8')
    def process_item(self,item,spider):
        lines = json.dumps(dict(item),ensure_ascii=False) + "\n"
        self.file.write(lines)
        return item
    def spider_close(self,spider):
        self.file.close()

__init__() 构造对象的时候,就打开文件,scrapy 会调用 process_item() 函数对数据进行处理,在这个函数中,将数据以 json 的格式写入文件中。操作完成之后,将文件关闭。思路很简单

下面的是采用scrapy自带的方法写入的类

####scrapy自带的写入本地json文件的类
class JsonExportPipline(object):
    def __init__(self):
        self.file = open('articleexport.json','wb')
        self.exporter = JsonItemExporter(self.file,encoding = "utf-8",ensure_ascii=False)
        self.exporter.start_exporting()
    def close_spider(self,spider):
        self.exporter.finish_exporting()
        self.file.close()
    def process_item(self,item,spider):
        self.exporter.export_item(item)
        return item

scrapy.exporters 提供了几种不同格式的文件支持,能够将数据输出到这些不同格式的文件中,查看 JsonItemExporter 源码即可获知

__all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter',

'CsvItemExporter', 'XmlItemExporter', 'JsonLinesItemExporter',

'JsonItemExporter', 'MarshalItemExporter']

这些就是 scrapy 支持的文件。方法名称都差不多,这算是 scrapy 运行 pipeline 的模式,只需要将逻辑处理放在 process_item()

scrapy 就会根据规则对数据进行处理。当然,要想使我们写的数据操作有效,别忘记了,在 settings.py 中进行配置

ITEM_PIPELINES = { 'scrapy.pipelines.images.ImagesPipeline': 1,

'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,

'ArticleSpider.pipelines.JsonExporterPipeline':3,}

将数据存储到 MySQL 数据库

前面介绍了将数据以 json 格式导出到文件,那么将数据保存到 MySQL 中,如何操作,相信大家已经差不多了然于胸了。这里也介

绍两种方法,一种是通过 MySQLdb 的API来实现的数据库存取操作,这种方法简单,适合用与数据量不大的场合,如果数据量大,

数据库操作的速度跟不上数据解析的速度,就会造成数据拥堵。那么使用第二种方法就更好,使用 twisted 框架提供的异步操作方

法,不会造成拥堵,速度更快。

既然是入 MySQL 数据库,首先肯定是需要创建数据库表了。表结构如下图所示: 

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第6张图片

上图中有一个字段的值,我没有讲述怎么取,就是 front_img_path 这个值,大家在数据库入库的时候,直接用空置或者空字符串

填充即可。这个字段是保存图片在本地保存的路径,这个需要在 ArticleImagePipeline 的 

item_completed(self, results, item, info)方法中的 results 参数中获取。

再我们的pipelines.py文件中新建一个类然后代码如下:

###这个类用于获取图片文件的路径
class ArticleImagePipeline(ImagesPipeline):
    def item_completed(self, results, item, info):
        for ok,value in results:
            image_file_path = value["path"]
        item["front_image_path"] = image_file_path
        return item
别忘记再setting中设置
Spider.pipelines.ArticleImagePipeline':4

好了,数据库表创建成功之后,下面就来将数据入库了。

导入 

import MySQLdb

import MySQLdb.cursors

1.MySQLdb的基础用法

####写入mysql的类
class MysqlExportPipline(object):
    #采用同步机制将数据写入mysql;
    def __init__(self):
        self.conn = MySQLdb.connect('localhost','root','123456','article_spider',charset ='utf8',use_unicode = True)
        self.cursor = self.conn.cursor()
    def process_item(self, item, spider):
        insert_sql = """
            insert into jobbole_article(title,create_date,url,url_object_id,front_img_url,comment_nums, fav_nums,prasie_nums,tags )
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
        """
        self.cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tags"]))
        self.conn.commit()
        #这里的exexute和commit是同步操作一条语句不执行完不会执行下一条语句。但是我们scrapy的解析速度很快如果入库的速度很慢的话就会造成阻塞。
    def spider_closed(self,spider):
        self.conn.close()

如果对 API 想了解的更多,就去阅读 python MySQLdb 的相关API文档说明,当然,要想这个生效,首先得在 settings.py 文件中将这个 pipeline 类加入 ITEM_PIPELINE 字典中

'Spider.pipelines.MysqlExportPipline':5,

2.通过 Twisted 框架提供的异步方法入库

这个方法使用前需要再settings中配置我们的mysql的参数

MYSQL_HOST = "localhost"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "123456"

#####采用Twisted框架提供的api接口进行mysql数据入库的异步操作!
class MysqlTwistedPipline(object):
    #异步机制将数据写入mysql;
    def __init__(self,dbpool):
        self.dbpool = dbpool
    #通过@classmethod这个方法定义一个from_settings函数(自定义主键和扩展的时候很有用!)
    # 被scrapy调用然后取再settings.py中设置的MYSQL的值
    @classmethod
    def from_settings(cls,settings):
        dbparms = dict(host = settings["MYSQL_HOST"],
        db = settings["MYSQL_DBNAME"],
        user = settings["MYSQL_USER"],
        passwd = settings["MYSQL_PASSWORD"],
        charset = "utf8",
        cursorclass = MySQLdb.cursors.DictCursor,
        use_unicode = True,
                       )
        dbpool = adbapi.ConnectionPool("MySQLdb",**dbparms)
        return cls(dbpool)
###上面两个函数主要用来传递dbpool
    def process_item(self, item, spider):
        #使用twisted将mysql插入变成一部执行
        query =self.dbpool.runInteraction(self.do_insert,item)
        query.addErrback(self.handle_error)  #处理异常
    def handle_error(self,failure):
        #处理一部插入的异常
        print(failure)
    def do_insert(self,cursor,item):
        #执行具体的插入
        insert_sql = """
                    insert into jobbole_article(title,create_date,url,url_object_id,front_img_url,comment_nums, fav_nums,prasie_nums,tags )
                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
                """
        cursor.execute(insert_sql, (
        item["title"], item["create_date"], item["url"], item["url_object_id"], item["front_image_url"],
        item["comment_nums"], item["fav_nums"], item["praise_nums"], item["tags"]))

博主也是近一个多星期开始学习爬虫的 scrapy 框架,对 Twisted 框架也不怎么熟悉,上面的代码是一个例子,大家可以看下注释,等以后了解更多会补充更多相关知识。

需要提到的是,上面定义的 from_settings(cls. settings) 这个类方法, scrapy 会从 settings.py 
文件中读取配置进行加载,这里将 MySQL 的一些配置信息放在了 settings.py 文件中,然后使用 from_settings 方法直接获取,在 settings.py 中需要添加如下代码:

我们一般使用的时候可以直接复制粘贴因为执行sql语句的是最后一个函数我们只需要更改sql语句就行!

需要注意的是def from_settings()函数中的字典的变量的命名是确定的如:

host db user passwd 等多个请CTRL+鼠标右键点击MySQLdb进入然后CTRL+鼠标右键进入Connection查看
我们最后看一下数据库内容

Scrapy爬取伯乐在线所有文章和图片并提取有用的数据_第7张图片

异步机制写的飞快!

这里需要给大家补充:

使用 itemloader

相信大家已经发现,虽然使用了 item,但是使用 css selecotor,我们的 parse 函数显得很长,而且,当数据量越来越大之后,一大堆的 css 表达式是很难维护的。在加上正则表达式的提取,代码会显得很臃肿。这里,给大家推荐是用 itemloader。itemloader 可以看成是一个容器。

首先我们需要再jobbole.py中导入一个包

from scrapy.loader import ItemLoader

item_loader = ItemLoader(item=JoblleArticleItem(),response=response)
初始化我们的item_loader

   为我们提供了

   item_loader.add_xpath() xpath方法  item_loader.add_css() css选择器  item_loader.add_value() 直接返回的值

    提取数据代码变为:

front_image_url = response.meta.get("front_image_url", "")
item_loader = ItemLoader(item=JoblleArticleItem(),response=response)
item_loader.add_xpath("title","//div[@class='entry-header']/h1/text()")
item_loader.add_value("url",response.url)
item_loader.add_value("url_object_id",get_md5(response.url))
item_loader.add_value("front_image_url",[front_image_url])
item_loader.add_xpath("create_date","//p[@class='entry-meta-hide-on-mobile']/text()")
item_loader.add_xpath("fav_nums","//span[contains(@class,'bookmark-btn')]/text()")
item_loader.add_xpath("praise_nums","//span[contains(@class,'vote-post-up')]/h10/text()")
item_loader.add_xpath("comment_nums","//a[@href='#article-comment']/span/text()")
item_loader.add_xpath("content","//div[@class='entry']")
item_loader.add_xpath("tags","//p[@class='entry-meta-hide-on-mobile']/a/text()")
article_item = item_loader.load_item()
yield article_item

 debug后发现每个字段都是list而且我们原先处理的数据都和没处理一样这时候我们需要去item里面写我们的数据处理方法类和函数!!!

 
  
from scrapy.loader.processors import MapCompose ,TakeFirst ,Join
#MapCompose(调用处理字段规则的函数),TakeFirst #(获得的数据是list,默认提取第一个),Join(我们提取的需要时整个列表时候可以使用)
from scrapy.loader import  ItemLoader

需要用到的自定义loader的类

然后我们新建一个

class AriticleItemLoader(ItemLoader):
    #自定义itemloader,默认取列表第一个值。
    default_output_processor = TakeFirst()

然后我们回到jobbole.py修改我们的item_loder继承的类使用我们新写的类

然后我们再JoblleArticleItem中对数据进行处理

def date_convert(value):
    #日期格式化处理
    try:
        create_date = datetime.datetime.strptime(value, '%Y/%m/%d').date()
    except Exception as e:
        create_date = datetime.datetime.now().date()
    return create_date

def get_nums(value):
    #正则匹配点赞数,评论数,收藏数
    match_re = re.match('.*?(\d+).*?', value)
    if match_re:
        nums = int(match_re.group(1))
    else:
        nums = 0
    return nums

def remove_comment_tags(value):
    #去掉tags中提取的评论
    if "评论" in value:
        return ""
    else:
        return value
def return_value(value):
    #用于我们需要传递一个列表但不改变值即不使用Join("")
    return
class AriticleItemLoader(ItemLoader):
    #自定义itemloader,默认取列表第一个值。
    default_output_processor = TakeFirst()
class JoblleArticleItem(scrapy.Item):
    title = scrapy.Field()  #Field()能够接收和传递任何类型的值,类似于字典的形式
    url = scrapy.Field()          #获取的文章的url
    url_object_id = scrapy.Field()#文章url的ID我们后面会使用
    front_image_url = scrapy.Field(
        #当我们将它交给scrapy的ImagePipline下载的时候必须是列表所以我们可以直接
        # 调用这个return_value函数覆盖自定义的itemloader中的default_output_processor = TakeFirst()
        out_processor = MapCompose(return_value)

    ) #文章的首个图面的URL当我们将它交给scrapy的ImagePipline下载的时候必须是列表所以
    front_image_path = scrapy.Field()#图片的保存路径
    create_date = scrapy.Field(
        input_processor = MapCompose(date_convert),  #这个类是用于处理字段如需要正则匹配或者别的,这个类的参数可以是多个处理函数!
        #output_processor = TakeFirst()  #调用这个类我们就只去列表第一个
    )     #发布日期
    praise_nums = scrapy.Field(
        input_processor = MapCompose(get_nums)
    )     #点赞数
    fav_nums = scrapy.Field(
        input_processor=MapCompose(get_nums)
    )        #收藏数
    comment_nums = scrapy.Field(
        input_processor=MapCompose(get_nums)
    )    #评论数
    content = scrapy.Field()         #文章原文
    tags = scrapy.Field(
        input_processor = MapCompose(remove_comment_tags),
        out_processor= Join(",")
    )            #文章标签
到此所有书写完成!!!

 
 

你可能感兴趣的:(Scrapy爬取伯乐在线所有文章和图片并提取有用的数据)