美文网首页
爬取知名技术网站

爬取知名技术网站

作者: 何惧l | 来源:发表于2018-05-02 18:43 被阅读73次

scrapy的基本使用

  1. pip install -i https://pypi.douban.com/simple/ scrapy
  2. 新建一个scrapy的项目,在cmd中,工作路径中
    • scrapy startproject ArticleSpider | 创建一个项目
    • 然后在pycharm中打开,可以看到一些基本的配置信息
    • cd ArticleSpider(项目名)
    • scrapy genspider jobbole blog.jobbole.com | 创建一个模板,指向这个域名
    • 此时在pycharm中就可以看到默认生成的代码(jobbole.py)
# -*- coding: utf-8 -*-
import scrapy

class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/']

    def parse(self, response):
        pass

虚拟环境的配置.png
  1. 启动,
    • 在cmd中scrapy项目的目录路径下输入scrapy crawl jobbole,这个名字和创建时候的名字一样,
    • 此时或报错,说缺少'win32api',安装
      pip install -i https://pypi.douban.com/simple pypiwin32,再此运行就可以了
    • 但是每次都打开cmd太麻烦了,此时设置一个scrapy的小脚本,这样方便运行,在根目录中新建一个main.py
# -*- coding: utf-8 -*-
from scrapy.cmdline import execute

import sys
import os

# 首先获取到当前文件的目录os.path.abspath(__file__)
# 然后os.path.dirname()获取到当前文件的上一节目录,也就是项目的目录
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# 然后执行命令,注意,里面传入的是一个数组
execute(["scrapy","crawl","jobbole"])

* 注意此时要把
* **settings.py文件中的ROBOTSTXT_OBEY属性设置为False**
* **pipelines.py是用于存储数据相关的东西**
* **middilewares.py是自定义的middilewares方法,处理响应的IO的操作**
* **init.py是项目的初始化文件**
* **items.py是我们要爬取的信息的相关属性,类似于表单,用来保存获取到的数据**

xpath提取数据

  1. xpath语法

  2. 简单调试,在cmd当前的项目路径下,后面加要爬取的网址
    scrapy shell http://blog.jobbole.com/113912/

    调试.png
    注意其中的extract()方法,提取数据,若数组为空会报异常、或者使用extract_first()方法提起第一个数据,就不会报异常
  3. 然后写入到pycharm中,在jobbole.py文件中

# -*- coding: utf-8 -*-
import scrapy
import re

class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    # 要爬取的连接,这里只爬取一页的数据
    # start_urls = ['http://blog.jobbole.com/']
    start_urls = ['http://blog.jobbole.com/113821/']

    # 这里的response就自带xpath,不用像之前那样,在这直接获取想要的值就可以了
    def parse(self, response):
        # 标题
        title = response.xpath('//*[@id="post-113912"]/div[1]/h1/text()').extract_first()
        # 创建的时候
        create_time = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract_first().strip().replace("·","").strip()

        # 此时使用了contains函数,代表要找一个class(第一个参数),属性里面包括某个值(第二个参数)
        # 点赞数
        praise_nums = int(response.xpath("//span[contains(@class,' vote-post-up')]/h10/text()").extract_first())

        # 使用这个就可以获取到收藏数,但是结果是这种格式,' 3 收藏',需要进行转换,使用正则
        # response.xpath("//span[contains(@class,'bookmark-btn')]/text()").extract()[0]
        fav_nums = response.xpath("//span[contains(@class,'bookmark-btn')]/text()").extract()[0]
        match_re = re.match(".*?(\d+).*",fav_nums)
        if match_re:
            fav_nums = int(match_re.group(1))
        else:
            match_re = 0

        # 评论数,这个和上面的一样,获取到的也是需要用正则 ' 1 评论'
        comment_nums = response.xpath("//a[@href='#article-comment']//span/text()").extract()[0]
        match_re = re.match(".*?(\d+).*", comment_nums)
        if match_re:
            comment_nums = int(match_re.group(1))
        else:
            comment_nums = 0

        # 这里把文章的信息全部取出来
        content = response.xpath("//div[@class='entry']").extract()[0]

        # 获取标签信息 ['IT技术', ' 1 评论 '],这里把评论结尾的进行过滤
        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)

        pass


css提取数据

和上面的一样,只是提取的方式不同而已

# -*- coding: utf-8 -*-
import scrapy
import re

class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    # 要爬取的连接,这里只爬取一页的数据
    # start_urls = ['http://blog.jobbole.com/']
    start_urls = ['http://blog.jobbole.com/113821/']

  
    def parse(self, response):

        # ::text打印出文字
        title = response.css(".entry-header h1::text").extract()[0]
        create_time = response.css(".entry-meta-hide-on-mobile::text").extract()[0].strip().replace(" ·","").strip()
        praise_nums = response.css(".vote-post-up h10::text").extract()[0]
        fav_nums = response.css(".bookmark-btn::text").extract()[0]
        match_re = re.match(".*?(\d+).*",fav_nums)
        if match_re:
            fav_nums = int(match_re.group(1))
        else:
            fav_nums = 0
        comment_nums = response.css("a[href='#article-comment']::text").extract()[0]
        match_re = re.match(".*?(\d+).*", comment_nums)
        if match_re:
            comment_nums = int(match_re.group(1))
        else:
            comment_nums = 0
        content = response.css("div.entry").extract()[0]
        tag_list =  response.css("p.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)

        pass

获取全部文章的url

  1. 放入主页面,首先获取到主页上每个文章的url,通过Request方法,callback=方法名,通过yield就一直解析
  2. 获取到下一页的链接,还是通过Request方法进行解析,调用的自身方法,通过yield就一直执行,直到最后的页面为空就结束了
# -*- coding: utf-8 -*-
import scrapy
import re
# 下载网页
from scrapy.http import Request
# parse拼接网址应对herf内有可能网址不全
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进行下载然后解析函数进行具体字段的解析
        2. 获取到下一页的url,并交给scrapy进行下载完成后交给parse函数
        :param response:
        :return:
        """
        # 解析列表页中文章的url并交给scrapy下载后进行解析
        post_urls =  response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()
        for post_url in post_urls:

            # 这里使用自带的这个方法,第一个参数是url,第二个参数是要执行的方法,这里传入方法名就可以。然后就会不断的执行这个方法
            # 使用parse是为域名补全,若获取的域名不全的话使用这个方法就极为简单,就不用再进行拼接了
            yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)

        # 提取下一页并交给scrapy进行下载
        next_url =  response.css(".next.page-numbers::attr(href)").extract_first("")
        if next_url:
            # 若下一页还存在的话,就再执行自身这个方法
            yield Request(url=parse.urljoin(response.url,next_url),callback=self.parse)



     # 提取文章的具体逻辑
    def parse_detail(self,response):
        # ::text打印出文字
        title = response.css(".entry-header h1::text").extract_first()
        create_time = response.css(".entry-meta-hide-on-mobile::text").extract_first().strip().replace(" ·","").strip()
        praise_nums = response.css(".vote-post-up h10::text").extract_first()
        fav_nums = response.css(".bookmark-btn::text").extract()[0]
        match_re = re.match(".*?(\d+).*",fav_nums)
        if match_re:
            fav_nums = int(match_re.group(1))
        else:
            fav_nums = 0
        comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0]
        match_re = re.match(".*?(\d+).*", comment_nums)
        if match_re:
            comment_nums = int(match_re.group(1))
        else:
            comment_nums = 0
        content = response.css("div.entry").extract_first()
        tag_list =  response.css("p.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)

        pass


数据处理

数据爬取的任务就是从非结构的数据中提取出结构性的数据。
items 可以让我们自定义自己的字段(类似于字典,但比字典的功能更齐全)

  1. 若文章有封面图该怎么获取呢,当前的写法
post_urls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()

改进后

    post_nodes =  response.css("#archive .floated-thumb .post-thumb a")
    for post_node in post_nodes:
            # 获取文章的封面图
            image_url = post_node.css("img::attr(src)").extract_first("")
            post_url = post_node.css("::attr(href)").extract_first("")

在下载网页的时候把获取到的封面图的url传给parse_detail的response,通过meta将它发送出去

    post_nodes =  response.css("#archive .floated-thumb .post-thumb a")
        for post_node in post_nodes:
            image_url = post_node.css("img::attr(src)").extract_first("")
            post_url = post_node.css("::attr(href)").extract_first("")
            # 这里meat参数是向resposne里传递参数,是一个字典,这样在parse_detail方法中就可以接收这个参数了
            yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)

在parse_detail函数中再接收这个数据

# 获取封面图片,注意这个是从Request中传递过来的
        front_image_url = response.meta.get("front_image_url","")

这样就可以获取到文章的封面图了

  1. items.py:自定义要获取数据,这样更加的方便

import scrapy

class ArticlespiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass

# 自定义item并在jobboled.py中进行填充
class JobBoleArticleItem(scrapy.Item):
    # 定义Field()属性,这样这个title就可以传递任何类型的数据了
    title = scrapy.Field()
    create_date = scrapy.Field()
    url = scrapy.Field()
    # 文章链接进行MD5
    url_object_id = scrapy.Field()
    # 获取到封面图
    front_image_url = scrapy.Field()
    # 封面图要保存的路径
    front_image_path = scrapy.Field()
    praise_nums = scrapy.Field()
    fav_nums = scrapy.Field()
    comment_nums = scrapy.Field()
    content = scrapy.Field()
    tags = scrapy.Field()
  1. 在jobboled.py中进行填充
# -*- coding: utf-8 -*-

from ArticleSpider.items import JobBoleArticleItem

class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/']
    def parse(self, response):
   
        post_nodes =  response.css("#archive .floated-thumb .post-thumb a")
        for post_node in post_nodes:
            image_url = post_node.css("img::attr(src)").extract_first("")
            post_url = post_node.css("::attr(href)").extract_first("")
            yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)

        # 提取下一页并交给scrapy进行下载
        next_url =  response.css(".next.page-numbers::attr(href)").extract_first("")
        if next_url:
            # 若下一页还存在的话,就再执行自身这个方法,
            yield Request(url=parse.urljoin(response.url,next_url),callback=self.parse)



     # 提取文章的具体逻辑
    def parse_detail(self,response):
        # 首先实例化自定义的item
        article_item = JobBoleArticleItem()
        # 获取封面图片,注意这个是从Request中传递过来的
        front_image_url = response.mate.get("front_image_url","")
        title = response.css(".entry-header h1::text").extract_first()
        create_date = response.css(".entry-meta-hide-on-mobile::text").extract_first().strip().replace(" ·","").strip()
        praise_nums = response.css(".vote-post-up h10::text").extract_first()
        fav_nums = response.css(".bookmark-btn::text").extract()[0]
        match_re = re.match(".*?(\d+).*",fav_nums)
        if match_re:
            fav_nums = int(match_re.group(1))
        else:
            fav_nums = 0
        comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0]
        match_re = re.match(".*?(\d+).*", comment_nums)
        if match_re:
            comment_nums = int(match_re.group(1))
        else:
            comment_nums = 0
        content = response.css("div.entry").extract_first()
        tag_list =  response.css("p.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)

        # 进行填充
        article_item['title'] = title
        article_item['url'] = response.url
        article_item['create_date'] = create_date
        # 注意,这里下载图片的是时候要使用数组括起来
        article_item['front_image_url'] = [front_image_url]
        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

        # 这里调用yield之后,这个article_item就会传递到 pipelines.py中
        yield article_item


  1. yield article_item将这个item传送到pipelines中
    • 首先在setting.py中将ITEM_PIPELINES注释去掉
ITEM_PIPELINES = {
   'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
}

这个时候item传输到pipeline就可以将其保存到数据库中的了
* setting设置下载图片

ITEM_PIPELINES = {
   'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
    'scrapy.pipeline.images.ImagesPipeline':1,
}
* 然后定义下载那个图片以及下载图片的地址,还可以对下载的图片进行约束

注意,后面的数字代表优先级,当有多个事件的时候,数值越小,优先级就越高,就越先执行

ITEM_PIPELINES = {
   'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
    'scrapy.pipeline.images.ImagesPipeline':1,
}
# 下载图片的时候要自己配置下载图片的url,这个就是item中获取到的图片的封面
IMAGES_URLS_FIELD = "front_image_url"
# 设置图片的保存路径相对路径或者结对路径
# 获取到当前文件的上一级目录
project_dir = os.path.abspath(os.path.dirname(__file__))
# 使用这个方法获取到images的目录,图片保存到这里
IMAGES_STORE = os.path.join(project_dir,"images")
# 对下载的图片进行约束
# IMAGES_MIN_HEIGHT = 100
# IMAGES_MIN_WIDTH = 100

注意:安装PIL pip install pillow
* 定制自己的pipeline使其下载图片后能保存下它的本地路径,在pipeline.py文件中

# -*- coding: utf-8 -*-


from scrapy.pipelines.images import ImagesPipeline
    ..
    #保持不变就可以
# 自定义的图片处理
class ArticlesImagePipeline(ImagesPipeline):
    # 继承自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中设置自定义的pipline,

ITEM_PIPELINES = {
   'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
    # 'scrapy.pipeline.images.ImagesPipeline':1,
    'ArticleSpider.pipelines.ArticlesImagePipeline':1,
}
* url的md5处理,新建一个utils文件夹,键一个common.py文件
# -*- coding: utf-8 -*-

import hashlib

def get_md5(url):
    # 注意,这里传入的如果是字符串(unicode)要转为utf-8
    if isinstance(url,str):
        url = url.encode("utf-8")
    m = hashlib.md5()
    m.update(url)
    return m.hexdigest()

然后在jobbole.py中将url的md5保存下来

from ArticleSpider.utils.common import get_md5

def parse_detail(self,response):
    ..
   article_item['url_object_id'] = get_md5(response.url)

数据保存

  1. 保存到本地json文件(自定义)
    • 使用了import codecs打开文件避免了一些编码问题
    • 在pipelines.py中自定义一个JsonWithEncodingPipeline来实现
import codecs
import json
class JsonWithEncodingPipeline(object):
    # 初始化的时候打开一个json文件,注意这里使用了codecs,
    def __init__(self):
        self.file = codecs.open('article.json','w',encoding='utf-8')
    # 这是默认的数据处理方法
    def process_item(self,item,spider):
        # 首先把这个item数据转换为json数据,这里要注意ensure_ascii设置为false,为了避免中文乱码,每一天数据加一个换行符
        lines = json.dumps(dict(item),ensure_ascii=False) + '\n'
        # 然后写入到指定的文件中
        self.file.write(lines)
        return item
    # 当spider关闭的时候就会调用这个方法
    def spider_closed(self,spider):
        self.file.close()
* 在setting.py中注册
ITEM_PIPELINES = {
   'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,
    # 'scrapy.pipeline.images.ImagesPipeline':1,
    'ArticleSpider.pipelines.ArticlesImagePipeline':1,
}

* 运行程序就可以看到![保存本地json文件.png](https://img.haomeiwen.com/i2840691/679f7156eebdd117.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  1. 使用scrapy内置的方法导出json数据
    • 导入from scrapy.exporters import JsonItemExporter
    • 在pipelines.py中自定义一个类来实现
from scrapy.exporters import JsonItemExporter
class JsonExporterPipleline(object):
    # 调用scrapy提供的json export导出json文件
    def __init__(self):
        self.file = open('articleexport.json','wb')
        self.export = JsonItemExporter(self.file,encoding="utf-8",ensure_ascii=False)
        self.export.start_exporting()

    def close_spider(self,spider):
        self.export.finish_exporting()
        self.file.close()

    def process_item(self, item, spider):
        self.export.export_item(item)
        return item

* 在setting.py中注册
ITEM_PIPELINES = {
   'ArticleSpider.pipelines.JsonExporterPipleline': 2,
    # 'scrapy.pipeline.images.ImagesPipeline':1,
    'ArticleSpider.pipelines.ArticlesImagePipeline':1,
}
  1. 保存数据到mysql中
    • 根据items中建立对应的数据表,但是在保存数据的时候日期用的是字符串,这个时候在还要进行日期的转换,在jobbole.py中
import datetime

def parse_detail(self,response):
    ..
    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

* 数据表设计
* 三个num字段均设置不能为空,然后默认0,content设置为longtext,主键设置为url_object_id
jobble数据表设计.png
* 安装数据库驱动pip install mysqlclient
* 保存到数据库pipeline(同步)编写
import MySQLdb
class MysqlPipline(object):
    # 类实例化的时候连接数据库
    def __init__(self):
        self.conn = MySQLdb.connect('127.0.0.1','root','123456','muke',charset="utf8",use_unicode=True)
        # 执行sql语句
        self.cursor = self.conn.cursor()

    # 重写这个方法,这里做个演示,插入了少量的数据
    def process_item(self, item, spider):
        insert_sql = """
            insert into jobbole_article(title,url,create_date,fav_nums) VALUES (%s,%s,%s,%s)
        """

        self.cursor.execute(insert_sql,(item["title"],item["url"],item["create_date"],item["fav_nums"]))
        self.conn.commit()

这个别忘了

ITEM_PIPELINES = {
    'ArticleSpider.pipelines.MysqlPipline':1,
}
* 保存到数据库的(异步Twisted)编写,因为我们的爬取速度可能大于数据库存储的速度,异步操作。
* 设置参数:在setting.py中设置
MYSQL_HOST = '127.0.0.1'
MYSQL_DBNAME = 'muke'
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'
* 还是在pipeline.py文件中,
import MySQLdb.cursors
from twisted.enterprise import adbapi

class MysqlTwistedPipline(object):

    def __init__(self,dbpool):
        self.dbpool = dbpool

    # 通过这个方法就可以获取到在settings总的定义的参数
    @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)


    # 重写这个方法
    def process_item(self, item, spider):
        # 使用twisted,将mysql插入变成异步执行,第一个参数是要执行具体逻辑,第二个是传递的参数
        query = self.dbpool.runInteraction(self.do_insert,item)
        # 处理异常
        query.addErrback(self.handle_error,item,spider)

    def handle_error(self,failure,item,spider):
        # 处理异步插入的异常
        print(failure)


    # 其他的基本不变,主要在这里执行不同的sql语句
    def do_insert(self,cursor,item):
        # 执行具体的插入逻辑
        insert_sql = """
              insert into jobbole_article(title,url,create_date,fav_nums) VALUES (%s,%s,%s,%s)
          """

        cursor.execute(insert_sql, (item["title"], item["url"], item["create_date"], item["fav_nums"]))


这个别忘了

ITEM_PIPELINES = {
    'ArticleSpider.pipelines.MysqlTwistedPipline':1,
}
  • 这样就使用了异步的方式来保存数据

ItemLoader

  1. itemloadr提供了一个容器,让我们配置某一个字段该使用哪种规则
from scrapy.loader import ItemLoader
 def parse_detail(self,response):

        # 获取封面图片,注意这个是从Request中传递过来的
        front_image_url = response.meta.get("front_image_url","")
     
        # 使用自定义的Loader
        # 通过ItemLoader加载item,最重要的两个参数,第一个是自定义的item
        item_loader = ItemLoader(item=JobBoleArticleItem(),response=response)
        # item中重要的方法
        # item_loader.add_css()
        # item_loader.add_xpath()
        # item_loader.add_value()
        # 第一个参数就是对应的自定义的item中的字段,第二个就是提取的规则
        item_loader.add_css('title','.entry-header h1::text')
        item_loader.add_css('create_date','.entry-meta-hide-on-mobile::text')
        item_loader.add_css('praise_nums','.vote-post-up h10::text')
        item_loader.add_css('fav_nums','.bookmark-btn::text')
        item_loader.add_css('comment_nums',"a[href='#article-comment'] span::text")
        item_loader.add_css('content',"a[href='#article-comment'] span::text")
        item_loader.add_css('tags',"p.entry-meta-hide-on-mobile a::text")
        # add_value()这个就是直接添加值
        item_loader.add_value('url',response.url)
        item_loader.add_value('url_object_id',get_md5(response.url))

         #调用这个方法来对规则进行解析生成item对象
        article_item = item_loader.load_item()


        # 这里调用yield之后,这个article_item就会传递到 pipelines.py中
        yield article_item

直接使用itemloader的问题.png
  • 所有的值变成了list
  • 对这些值要做一些处理函数( input_processor = MapCompose())
  1. 在item.py中进行处理,

from scrapy.loader.processors import MapCompose


class ArticlespiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass


class JobBoleArticleItem(scrapy.Item):
    # 定义Field()属性,这样这个title就可以传递任何类型的数据了,常用的两个参数
    title = scrapy.Field(
        # 当这个值传进来的时候,可以进行预处理,在方法中,可以添加处理多个函数,从左到右执行
        input_processor = MapCompose()
    )

* 自定义itemloader实现默认提取第一个
from scrapy.loader import ItemLoader
class ArticleItemLoader(ItemLoader):
    # 自定义itemloader实现默认提取第一个
    default_output_processor = TakeFirst()
* 此时在jobbole.py中要使用我们自定义的loader
from ArticleSpider.items import JobBoleArticleItem,ArticleItemLoader
  item_loader = ArticleItemLoader(item=JobBoleArticleItem(),response=response)
* 下载图片pipeline增加if增强通用性
class ArticleImagePipeline(ImagesPipeline):
    #重写该方法可从result中获取到图片的实际下载地址
    def item_completed(self, results, item, info):
        if "front_image_url" in item:
            for ok, value in results:
                image_file_path = value["path"]
            item["front_image_path"] = image_file_path

        return item
  • items.py全部代码
# -*- coding: utf-8 -*-

import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose,TakeFirst,Join
import datetime
import re

class ArticlespiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass

class ArticleItemLoader(ItemLoader):
    # 自定义itemloader实现默认提取第一个
    default_output_processor = TakeFirst()


# 这里传入value就是获取到的参数,日期处理
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):
    # 去掉tag中提起的评论
    if "评论" in value:
        return ""
    else:
        return value
# list保存原值
def return_value(value):
    return value

class JobBoleArticleItem(scrapy.Item):
    # 定义Field()属性,这样这个title就可以传递任何类型的数据了,常用的两个参数
    title = scrapy.Field(
        # 当这个值传进来的时候,可以进行预处理,在方法中,可以添加处理多个函数,从左到右执行
        input_processor = MapCompose()
    )
    create_date = scrapy.Field(
        input_processor=MapCompose(date_convert),
        # 因为是数组,所以取一个数据,自定义itemloader后就不用这样写了
        output_processor = TakeFirst()
    )
    url = scrapy.Field()
    # 文章链接进行MD5
    url_object_id = scrapy.Field()
    # 获取到封面图,这个字段必须是一个list,此时在msyql插入语句中,要获取到第一个值
    front_image_url = scrapy.Field(
        # 默认的覆盖掉
        output_processor=MapCompose(return_value),
    )
    # 封面图要保存的路径
    front_image_path = scrapy.Field()
    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()
    #因为tag本身是list,所以要重写
    tags = scrapy.Field(
        input_processor=MapCompose(remove_comment_tags),
        output_processor=Join(',')
    )

相关文章

网友评论

      本文标题:爬取知名技术网站

      本文链接:https://www.haomeiwen.com/subject/dnncrftx.html