python爬虫之Scrapy框架(二)

Scrapy框架

1.CrawlSpider

在Scrapy框架中,提供了一个CrawlSpider爬虫,这个爬虫会自动对所有符合特定条件的url地址进行爬取,我们无需再通过yield Request的方式爬取。

我们首先创建一个项目,在项目目录下使用下面的代码创建一个CrawlSpider,

scrapy genspider -t crawl 爬虫名称 "目标url二级域名"

创建好后,我们会在spiders文件夹下,找到爬虫文件,Scrapy提供的代码如下,

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class WxxcxSpider(CrawlSpider):
    name = 'wxxcx'
    allowed_domains = ['wxapp-union.com']
    start_urls = ['http://www.wxapp-union.com/']

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        item = {}
        #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
        #item['name'] = response.xpath('//div[@id="name"]').get()
        #item['description'] = response.xpath('//div[@id="description"]').get()
        return item

我们可以看到,相比较普通的爬虫,crawlspider多了一个rules变量,rules变量中的allow参数是目标url的格式,可以使用正则来表示url条件;callback参数是当url符合allow条件时,爬虫回调的函数;follow参数表示是否对url页面中的url进行进一步爬取,即url是否是最终的子页面。

我们这里通过crawlspider对微信小程序社区的页面进行爬取,获取页面的标题、作者、发布时间和内容,

首先,我们先建立好item模型,

import scrapy


class WxxcxItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
    pub_time = scrapy.Field()
    content = scrapy.Field()

我们打开微信小程序社区,我们查看url可以发现,文章列表的url格式是“http://www.wxapp-union.com/portal.php?mod=list&catid=1&page=X”,其中X表示页数,所以,我们将start_urls参数设置为“http://www.wxapp-union.com/portal.php?mod=list&catid=1&page=1”;将rules变量中的allow参数设置为r’.+mod=list&catid=1&page=\d’,意为只要page参数值是数字,即为目标url。但是,我们只是获取了目标文章列表url,其并不是我们的最终目标url,所以,我们不回调函数,继续对url中的url进行爬取。所以follow参数值为True。

我们打开几篇文章,查看他们的url,例如,“http://www.wxapp-union.com/article-5947-1.html”,“http://www.wxapp-union.com/article-6146-1.html”。可以看出,只有article后面的数值发生了变化,我们将rules变量中的allow参数设置为r’.+article-.+\.html’,注意,.需要转义。因为是最终url,我们将follow参数值为False,并回调函数。

爬虫代码如下,

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import WxxcxItem


class WxxcxSpider(CrawlSpider):
    name = 'wxxcx'
    allowed_domains = ['wxapp-union.com']
    start_urls = ['http://www.wxapp-union.com/portal.php?mod=list&catid=1&page=1']

    rules = (
        Rule(LinkExtractor(allow=r'.+mod=list&catid=1&page=\d'), follow=True),
        Rule(LinkExtractor(allow=r'.+article-.+\.html'), callback='parse_detail', follow=False),
    )

	    def parse_detail(self, response):
	        title = response.xpath('//h1[@class="ph"]/text()').get()
	        author_p = response.xpath('//p[@class="authors"]')
	        author = author_p.xpath('.//a/text()').get()
	        pub_time = author_p.xpath('.//span/text()').get()
	        content = response.xpath('//td[@id="article_content"]/p/text()').getall()
	        content = "".join(content).strip()
	        item = WxxcxItem(title=title, author=author, pub_time=pub_time, content=content)
	        yield item  # 注意返回item,使用yield,不要使用return

pipelines文件代码如下,

from itemadapter import ItemAdapter
from scrapy.exporters import JsonLinesItemExporter


class WxxcxPipeline:
    def __init__(self):
        self.fp = open("wxxcx.json", "wb")
        self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')
        self.exporter.start_exporting()

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

    def close_spider(self, spider):
        self.fp.close()

最后,将settings文件中的ROBOTSTXT_OBEY、DOWNLOAD_DELAY、DEFAULT_REQUEST_HEADERS和ITEM_PIPELINES取消注释,并修改相应的值。

2.保存文件到指定目录

如果,我们需要将页面中的图片按照页面标题的名称保存到相应的目录下,我们需要对pipelines管道文件尽心修改。

我们可以使用__file__获取当前文件位置,再通过os模块的path.dirname()找到上一级文件目录,使用os模块的path.join()方法对文件目录进行拼接,如果需要创建文件夹则使用os模块的mkdir()方法。

下面,我们下面爬取斗图啦网站的图片,并按照他们相应的标题创建文件夹并保存。

首先,创建item模型,

import scrapy


class DtlItem(scrapy.Item):
    category = scrapy.Field()  # 图片标题
    img_url = scrapy.Field()  # 图片下载链接

爬虫文件,

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import DtlItem


class DtlSpider(CrawlSpider):
    name = 'dtl'
    allowed_domains = ['doutula.com']
    start_urls = ['https://www.doutula.com/article/list/?page=1']

    rules = (
        Rule(LinkExtractor(allow=r'.+page=\d'), callback='parse_urls', follow=False),
    )

    def parse_urls(self, response):
        item = {}
        aes = response.xpath('//a[@class="list-group-item random_list tg-article"]')
        for a in aes:
            category = a.xpath('.//div[@class="random_title"]/text()').get()
            images = a.xpath('.//img[@class!="gif"]/@data-original').getall()
            for image in images:
                img_url = image
                item = DtlItem(category=category, img_url=img_url)
                yield item

pipelines管道文件,

class DtlPipeline:
    def __init__(self):
    	# 寻找图片主文件夹,如果没有,则创建
        self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "images")
        if not os.path.exists(self.path):
            os.mkdir(self.path)

    def process_item(self, item, spider):
        category = item['category']  # 获取图片标题
        img_url = item['img_url']  # 获取图片下载地址
        image_name = img_url.split("/")[-1]  # 获取图片名称
        # 寻找图片相应的文件夹,如果没有,则创建
        category_path = os.path.join(self.path, category)
        if not os.path.exists(category_path):
            os.mkdir(category_path)
        request.urlretrieve(img_url, os.path.join(category_path, image_name))  # 下载图片
        return item

3.Scrapy内置下载器

Scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的item pipelines。这些pipeline有些共同的方法和结构(我们称之为media pipeline)。一般来说你会使用Files Pipeline或者Images Pipeline

Scrapy内置下载器有以下优点,

  1. 避免重新下载最近已经下载过的文件
  2. 可以方便的指定文件存储的路径
  3. 可以将下载的图片转换成通用的格式。比如,png、jpg
  4. 可以方便的生成缩略图
  5. 可以方便的检测图片的宽和高,确保他们满足最小限制
  6. 异步下载,效率非常高

使用Files Pipeline下载文件的时候,按照以下步骤来完成:

  1. 定义好一个Item,然后在这个item中定义两个属性,分别为file_urls以及filesfile_urls是用来存储需要下载的文件的url链接,需要给一个列表
  2. 当文件下载完成后,会把文件下载的相关信息存储到itemfiles属性中。比如下载路径、下载的url和文件的校验码等
  3. 在配置文件settings.py中配置FILES_STORE,这个配置是用来设置文件下载下来的路径
    10.启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines.files.FilesPipeline:1,并将原有字段注释

当使用Images Pipeline下载文件的时候,按照以下步骤来完成:

  1. 定义好一个Item,然后在这个item中定义两个属性,分别为image_urls以及imagesimage_urls是用来存储需要下载的图片的url链接,需要给一个列表
  2. 当文件下载完成后,会把文件下载的相关信息存储到itemimages属性中。比如下载路径、下载的url和图片的校验码等
  3. 在配置文件settings.py中配置IMAGES_STORE,这个配置是用来设置图片下载下来的路径
  4. 启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines.images.ImagesPipeline:1,并将原有字段注释

但是,Scrapy内置下载器会将图片全部储存到一个名为full的文件夹下,我们需要重写方法。

我们创建一个类并继承于scrapy.pipelines.images.ImagesPipeline类,我们查看ImagesPipeline类的代码,并搜索“full”,可以找到这样的代码,

 def file_path(self, request, response=None, info=None):
        image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
        return 'full/%s.jpg' % (image_guid)

说明这段代码返回的是图片存储位置,我们重写这段代码。

继续分析代码,

    def get_media_requests(self, item, info):
        urls = ItemAdapter(item).get(self.images_urls_field, [])
        return [Request(u) for u in urls]

这段代码是用来返回图片下载链接的,我们同样需要重写。

重写之后的pipelines文件代码如下,

class DtlImagesPipeline(ImagesPipeline):
    # 获取图片下载链接
    def get_media_requests(self, item, info):
        request_objs = super(DtlImagesPipeline, self).get_media_requests(item, info)
        # 将item中的信息绑定到request对象上,以便获取图片标题
        for request_obj in request_objs:
            request_obj.item = item
        return request_objs

    # 返回图片存储路径
    def file_path(self, request, response=None, info=None):
        path = super(DtlImagesPipeline, self).file_path(request, response, info)
        category = request.item.get('category')  # 获取request对象上的图片标题信息
        image_store = settings.IMAGES_STORE  # 设定存储图片主目录位置
        category_path = os.path.join(image_store, category)  # 根据图片标题寻找文件夹,如果没有,创建
        if not os.path.exists(category_path):
            os.mkdir(category_path)
        image_name = path.replace('full/', "")  # 获取图片名称
        image_path = os.path.join(category_path, image_name)
        return image_path

items文件代码如下,

import scrapy


class DtlItem(scrapy.Item):
    category = scrapy.Field()
    image_urls = scrapy.Field()
    images = scrapy.Field()

爬虫文件代码如下,

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import DtlItem


class DtlSpider(CrawlSpider):
    name = 'dtl'
    allowed_domains = ['doutula.com']
    start_urls = ['https://www.doutula.com/article/list/?page=1']

    rules = (
        Rule(LinkExtractor(allow=r'.+page=\d'), callback='parse_urls', follow=False),
    )

    def parse_urls(self, response):
        aes = response.xpath('//a[@class="list-group-item random_list tg-article"]')
        for a in aes:
            category = a.xpath('.//div[@class="random_title"]/text()').get()
            images = a.xpath('.//img[@class!="gif"]/@data-original').getall()
            images = list(map(lambda url: response.urljoin(url), images))  # 返回下载链接列表
            item = DtlItem(category=category, image_urls=images)
            yield item

settings文件中的ITEM_PIPELINES参数和IMAGES_STORE参数如下,

import os

ITEM_PIPELINES = {
   'test.pipelines.DtlImagesPipeline': 300,
}

IMAGES_STORE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')

你可能感兴趣的:(python爬虫之Scrapy框架(二))