目标是爬取某一系列图书的信息,例如名称、价格、图片等。
在这里插入图片描述

一、创建scrapy项目

在PyCharm终端依次输入:

  1. scrapy startproject dangdang
  2. cd dangdang\dangdang
  3. scrapy genspider dang category.dangdang.com/cp01.25.16.00.00.00.html

注:genspider后面的网址不需要带http://和url最后的斜线,如果带上需要去dangdang.py将start_urls手动修改为正确的url。.html的网页末尾一定不要有‘/’

之后测试一下这个url有没有robots协议或者反爬手段等,将parse函数改为输出一行文字,终端scrapy crawl dang发现运行成功,出现这行文字,连君子协议都没有。


二、items.py中定义数据的结构

通俗的说就是定义要下载的数据都有什么

给的案例是name = scrapy.Field(),定义完成的items.py如下

import scrapy

class DangdangItem(scrapy.Item):
    src = scrapy.Field()  # 图片
    name = scrapy.Field()  # 书名
    price = scrapy.Field()  # 价格

三、定位、爬取数据

分析网页源代码,我们需要的数据在如下的标签中
在这里插入图片描述

用xpath解析找到三个我们需要的信息

        src = //ul[@id="component_59"]//li//img/@src
        name = //ul[@id="component_59"]//li//img/@alt
        price = //ul[@id="component_59"]//li//p[@class="price"]/span[1]/text()

在这里有个小技巧,所有的selector对象都可以再次调用xpath,所以我们可以这样写:

    def parse(self, response):
        target_list = response.xpath('//ul[@id="component_59"]//li')

        for t in target_list:
            src = t.xpath('.//img/@src').extract_first()
            name = t.xpath('.//img/@alt').extract_first()
            price = t.xpath('.//p[@class="price"]/span[1]/text()').extract_first()
            print(src, name, price)

运行测试,暂时的输出结果如下。发现其他正常,图片的src除了第一个都是none.jpg,说明做了反爬(大概率是图片的懒加载)
在这里插入图片描述
分析其余59本书的图片发现,果然src的地址都一样,但当页面滑动到加载那张图片时,图片的src会立马变成data-original里面的地址,这也是懒加载的一种方式。
在这里插入图片描述
但还要注意,第一张图片没有做懒加载,所以需要一个判读语句,完整代码如下:

    def parse(self, response):
        target_list = response.xpath('//ul[@id="component_59"]//li')

        for t in target_list:
            src = t.xpath('.//img/@data-original').extract_first()
            if src is None:
                src = t.xpath('.//img/@src').extract_first()
            else:
                src = src
            name = t.xpath('.//img/@alt').extract_first()
            price = t.xpath('.//p[@class="price"]/span[1]/text()').extract_first()
            print(src, name, price)

四、保存数据到管道

接上,在刚才for循环的最后,首先存到一个book对象里,然后每创建一个就交给管道一个。这里的DangdangItem即在items.py中我们定义数据的结构的地方,导入这个类即可。导入方法为from dangdang.items import DangdangItem 编译器会报错,不用管,没关系!!!

			......
			name = t.xpath('.//img/@alt').extract_first()
            price = t.xpath('.//p[@class="price"]/span[1]/text()').extract_first()

			book = DangdangItem(src=src, name=name, price=price)

			# 获取一个book就将一个book交给pipelines
			yield book

之后我们需要用到管道,但必须要先在settings.py中设置开启管道,将settings中的如下代码行解开注释即可(这里的300代表优先级,优先级数值范围是1-1000,值越低优先级越高)。

ITEM_PIPELINES = {
   'dangdang.pipelines.DangdangPipeline': 300,
}

最后在pipelines.py中保存爬取数据,这里有两种写法。

首先第一种,因为yield是迭代器,产生一个book就交到pipelines执行一次process_item函数,所以文件会好多次被打开。

  1. 这里的写入模式我们不能用’w’,否则会被覆盖。
  2. write方法必须要写入str字符串类型,所以要强转
class DangdangPipeline:
    # item就是yield传来的book对象
    def process_item(self, item, spider):
        with open('book.json', 'a', encoding='utf-8') as f:
            f.write(str(item))

        return item

第二种,借助open_spider()和close_spider()函数。仅打开一次文件,保持打开状态,每个book不断写入,最后关闭文件。

class DangdangPipeline:
    # 在爬虫文件执行开始前,执行的函数
    def open_spider(self, spider):
        self.f = open('book.json', 'w', encoding='utf-8')

    # item就是yield传来的book对象
    def process_item(self, item, spider):
        self.f.write(str(item))
        return item

    # 在爬虫文件执行完后,执行的函数
    def close_spider(self, spider):
        self.f.close()

推荐第二种方式,因为第一种方式对文件的操作过于频繁,需要开关文件次数太多


五、多条管道下载

只需模仿DangdangPipeline类另写一个类,再在settings.py中开启管道即可

import urllib.request

class DangdangDownloadPipeline:
    def process_item(self, item, spider):

        url = 'http:' + item.get('src')
        filename = './books/' + item.get('name') + '.jpg'

        urllib.request.urlretrieve(url=url, filename=filename)
        return item
ITEM_PIPELINES = {
   'dangdang.pipelines.DangdangPipeline': 300,
   'dangdang.pipelines.DangdangDownloadPipeline': 301
}

六、多页数据的下载

分析第一页、第二页、第三页的url发现很相似,只是pg后面的数字在改变

我们的代码逻辑没有变,只有url在改变
所以我们需要改变页码,然后不断回调parse函数执行相同的逻辑。dang.py完整代码如下:

import scrapy
from dangdang.items import DangdangItem

class DangSpider(scrapy.Spider):
    name = 'dang'
    # 如果多页下载,需要调整allowed_domains的范围,一般情况下只写域名
    allowed_domains = ['category.dangdang.com']
    start_urls = ['http://category.dangdang.com/cp01.25.16.00.00.00.html']

    base_url = 'http://category.dangdang.com/pg'
    page = 1

    def parse(self, response):
        target_list = response.xpath('//ul[@id="component_59"]//li')

        for t in target_list:
            src = t.xpath('.//img/@data-original').extract_first()
            if src is None:
                src = t.xpath('.//img/@src').extract_first()
            else:
                src = src
            name = t.xpath('.//img/@alt').extract_first()
            price = t.xpath('.//p[@class="price"]/span[1]/text()').extract_first()

            book = DangdangItem(src=src, name=name, price=price)

            # 获取一个book就将一个book交给pipelines
            yield book

        if self.page < 100:
            self.page = self.page + 1

            url = self.base_url + str(self.page) + '-cp01.25.16.00.00.00.html'
            # 调用parse方法
            # scrapy.Request就是scrapy的get请求, url是请求地址,callback是要执行的函数
            yield scrapy.Request(url=url, callback=self.parse)

yield可以让parse函数不断在回调自己。


七、其他代码

Items.py

import scrapy


class DangdangItem(scrapy.Item):
    src = scrapy.Field()  # 图片
    name = scrapy.Field()  # 书名
    price = scrapy.Field()  # 价格

pipelines.py

from itemadapter import ItemAdapter

class DangdangPipeline:

    # 在爬虫文件执行开始前,执行的函数
    def open_spider(self, spider):
        self.f = open('book.json', 'w', encoding='utf-8')

    # item就是yield传来的book对象
    def process_item(self, item, spider):
        self.f.write(str(item))
        return item

    # 在爬虫文件执行完后,执行的函数
    def close_spider(self, spider):
        self.f.close()

import urllib.request

class DangdangDownloadPipeline:
    def process_item(self, item, spider):

        url = 'http:' + item.get('src')
        filename = './books/' + item.get('name') + '.jpg'

        urllib.request.urlretrieve(url=url, filename=filename)
        return item

步骤总结

  1. 创建scrapy项目
  2. 在items.py中定义所需数据的结构
  3. 利用response.xpath定位爬取数据
  4. 利用管道下载数据:首先创建一个要存储的对象book = DangdangItem(src=src, name=name, price=price)。然后用yield将其逐个交给pipelines。然后在settings.py里开启管道。pipelines.py中的process_item函数中的item就是这里的book对象。在这里面操作并存储数据。
  5. 多管道下载只需在pipelines.py中再定义一个类,同样也需要开启管道。
  6. 下载多页数据需要先设置一个页码变量,在parse函数中回调自己,过程中页码加一。需要用到yield关键字和scrapy.Request(url=url, callback=self.parse),即scrapy的get请求,callback是回调的函数名,不需要加括号。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐