Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据

鳄鱼君

发表文章数:642

Vieu四代商业主题

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

¥69 现在购买
首页 » Python » Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据

确保能够明白这篇文章,需要掌握XPATH语法,了解基本的HTML结构,会分析网页,知道如何使用Python中的函数,以及Python中的深拷贝和浅拷贝,重点是scrapy.Request()函数如何使用,自行查漏补缺。

好像基本学习Scrapy爬虫的,都会爬苏宁的图书,苏宁会有崩的一天!我们废话不多说,这篇文章是主要演示怎样实现翻页,抓取详情页数据,具体使用xpath解析不作为重点。苏宁图书目前网页如图:

Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据

首先创建爬虫:

scrapy startproject book
cd book
scrapy genspider subook book.suning.com

然后我们重点就是放在爬虫文件中去,我们首先看苏宁图书中的html是否存在当前屏幕上显示的所有内容,最简单的方式就是,右键查看源代码,在源代码中搜索我们在苏宁图书首页可以看到的内容,这个是都可以搜索到的。没问题之后我们就开始编写爬虫文件,parse函数用来解析大分类和小分类,分类列表页的url地址:

    def parse(self, response):
        #xpath通常都是先抓大后抓小,先找到大的div标签,然后循环找小的div标签
        #1.大分类分组
        categary=response.xpath('//div[@class="left-menu-container"]/div[1]/div[@class="menu-item"]')
        for li in categary:
            item={}#定义一个字典,存储数据
            item['big_cate']=li.xpath('./dl/dt/h3/a/text()').extract_first()#提取大分类的文本
            #小分类分组
            a_list=li.xpath('./dl/dd')
            for a in  a_list:
               item['s_href']=a.xpath('./a/@href').extract_first()#提取分类图书列表页的url地址
               item['s_cate']=a.xpath('./a/text()').extract_first()#提取小分类的文本
               if item['s_href'] is not None:#判断url是否为空值
                    #构造解析分类图书列表页的解析方法,并传递item到对应的函数
                   yield scrapy.Request(
                       item['s_href'],
                       callback=self.parse_book_list,
                       meta={"item":item}
                   )#key值可自定义,value就是item

然后是图书列表页,我们需要提取图书的img,图书的名字,图书的价格,图书详情页的url

#解析分类图书列表页数据
    def parse_book_list(self,response):
        item=response.meta["item"]#首先获取item
        #图书列表页分组
        book_list=response.xpath('//div[@id="filter-results"]/ul/li')
        for b in book_list:
            #book_img存在src属性和src2属性,所以需要处理
            #book_price存在em标签下的
            #<b>¥</b>100<i>.00</i><i><i></i></i></em>
            #但是在html标签中是没有<b>¥</b>100<i>.00</i><i><i></i></i>的所以提取不到价格
            item['book_img']='http:'+b.xpath('./div/div/div/div[@class="res-img"]/div/a/img/@src|./div/div/div/div[@class="res-img"]/div/a/img/@src2').extract_first()
            #book图片的url地址不完整,这里补充一下
            item['book_href']=b.xpath('./div/div/div/div[@class="res-img"]/div/a/@href').extract_first()
            item['book_price']=b.xpath('./div/div/div/div[@class="res-info"]/p[1]/em').extract_first()
            item['book_content']=b.xpath('./div/div/div/div[2]/p[2]/a/text()').extract_first().strip()
            yield scrapy.Request(
                'http:'+item['book_href'],
                callback=self.parse_book_detail,
                meta={"item":item}

            )

Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据

图书详情页的价格是不在我们看到的html标签结构中,存在js中,所以我们使用正则最方便:

Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据

#解析图书详情页的数据
    def parse_book_detail(self,response):
        item=response.meta['item']
        #书详情页提取,价格,
        item['book_author']=response.xpath('//div[@id="proinfoMain"]/ul/li/text()').extract_first().strip()
        item['book_pri']=re.findall('"itemPrice":"(.*?)",',response.body.decode(),re.S)
        item['book_pri']=item['book_pri'][0] if len(item['book_pri'])>0 else None
        print(item)

那么我是在最后进行打印item的,你可以每实现一个解析函数就打印一下,看看数据是否正确,xpath也是花点时间写的,那么我们最终的代码:

import scrapy
import re
class SubookSpider(scrapy.Spider):
    name = 'subook'
    allowed_domains = ['book.suning.com','list.suning.com','product.suning.com',]
    start_urls = ['http://book.suning.com/']

    def parse(self, response):
        #xpath通常都是先抓大后抓小,先找到大的div标签,然后循环找小的div标签
        #1.大分类分组
        categary=response.xpath('//div[@class="left-menu-container"]/div[1]/div[@class="menu-item"]')
        for li in categary:
            item={}#定义一个字典,存储数据
            item['big_cate']=li.xpath('./dl/dt/h3/a/text()').extract_first()#提取大分类的文本
            #小分类分组
            a_list=li.xpath('./dl/dd')
            for a in  a_list:
               item['s_href']=a.xpath('./a/@href').extract_first()#提取分类图书列表页的url地址
               item['s_cate']=a.xpath('./a/text()').extract_first()#提取小分类的文本
               if item['s_href'] is not None:#判断url是否为空值
                    #构造解析分类图书列表页的解析方法,并传递item到对应的函数
                   yield scrapy.Request(
                       item['s_href'],
                       callback=self.parse_book_list,
                       meta={"item":item}
                   )#key值可自定义,value就是item
    #解析分类图书列表页数据
    def parse_book_list(self,response):
        item=response.meta["item"]#首先获取item
        #图书列表页分组
        book_list=response.xpath('//div[@id="filter-results"]/ul/li')
        for b in book_list:
            #book_img存在src属性和src2属性,所以需要处理
            #book_price存在em标签下的
            #<b>¥</b>100<i>.00</i><i><i></i></i></em>
            #但是在html标签中是没有<b>¥</b>100<i>.00</i><i><i></i></i>的所以提取不到价格
            item['book_img']='http:'+b.xpath('./div/div/div/div[@class="res-img"]/div/a/img/@src|./div/div/div/div[@class="res-img"]/div/a/img/@src2').extract_first()
            #book图片的url地址不完整,这里补充一下
            item['book_href']=b.xpath('./div/div/div/div[@class="res-img"]/div/a/@href').extract_first()
            item['book_price']=b.xpath('./div/div/div/div[@class="res-info"]/p[1]/em').extract_first()
            item['book_content']=b.xpath('./div/div/div/div[2]/p[2]/a/text()').extract_first().strip()
            yield scrapy.Request(
                'http:'+item['book_href'],
                callback=self.parse_book_detail,
                meta={"item":item}

            )
        
    #解析图书详情页的数据
    def parse_book_detail(self,response):
        item=response.meta['item']
        #书详情页提取,价格,
        item['book_author']=response.xpath('//div[@id="proinfoMain"]/ul/li/text()').extract_first().strip()
        item['book_pri']=re.findall('"itemPrice":"(.*?)",',response.body.decode(),re.S)
        item['book_pri']=item['book_pri'][0] if len(item['book_pri'])>0 else None
        print(item)

需要注意allow_domains,苏宁每部分的内容url是不一样的,你需要添加到允许的范围内,否则就会过滤掉,那么有的url还是不完整的,需要自行拼接。

好了,爬虫代码已经完成了,但是我们在运行和调试的时候,我们这里在提取图书列表页的时候就发现了,数据很多都是相同的内容,这就尴尬了,为什么呢???

Scrapy爬虫框架采用的是多线程的异步爬虫,代码的执行顺序不是从上往下按照顺序执行的,那么在进行item字典中数据的传递的时候,它不会保证item所有的信息提取完成在进行下一次循环,可能在前一次for循环的时候,item正在获取某些数据,比方说价格,内容了什么的,那么后一次for循环就开始了,由于item是个字典类型,字典的特性之一就是:不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住,那么就会出现重复数据的情况。

说了那么多,就两点,Scrapy是个异步爬虫,字典的特性就是不允许同一个键出现两次。

解决方式很简单,就是使用深拷贝:重新开辟一个内存地址,具体更多关于深拷贝和浅拷贝的内容可参考:https://www.e1yu.com/7331.html。我们修改爬虫代码为:

import scrapy
import re
from copy import deepcopy
class SubookSpider(scrapy.Spider):
    name = 'subook'
    allowed_domains = ['book.suning.com','list.suning.com','product.suning.com',]
    start_urls = ['http://book.suning.com/']

    def parse(self, response):
        #xpath通常都是先抓大后抓小,先找到大的div标签,然后循环找小的div标签
        #1.大分类分组
        categary=response.xpath('//div[@class="left-menu-container"]/div[1]/div[@class="menu-item"]')
        for li in categary:
            item={}#定义一个字典,存储数据
            item['big_cate']=li.xpath('./dl/dt/h3/a/text()').extract_first()#提取大分类的文本
            #小分类分组
            a_list=li.xpath('./dl/dd')
            for a in  a_list:
               item['s_href']=a.xpath('./a/@href').extract_first()#提取分类图书列表页的url地址
               item['s_cate']=a.xpath('./a/text()').extract_first()#提取小分类的文本
               if item['s_href'] is not None:#判断url是否为空值
                    #构造解析分类图书列表页的解析方法,并传递item到对应的函数
                   yield scrapy.Request(
                       item['s_href'],
                       callback=self.parse_book_list,
                       meta={"item":deepcopy(item)}
                   )#key值可自定义,value就是item
    #解析分类图书列表页数据
    def parse_book_list(self,response):
        item=response.meta["item"]#首先获取item
        #图书列表页分组
        book_list=response.xpath('//div[@id="filter-results"]/ul/li')
        for b in book_list:
            #book_img存在src属性和src2属性,所以需要处理
            #book_price存在em标签下的
            #<b>¥</b>100<i>.00</i><i><i></i></i></em>
            #但是在html标签中是没有<b>¥</b>100<i>.00</i><i><i></i></i>的所以提取不到价格
            item['book_img']='http:'+b.xpath('./div/div/div/div[@class="res-img"]/div/a/img/@src|./div/div/div/div[@class="res-img"]/div/a/img/@src2').extract_first()
            #book图片的url地址不完整,这里补充一下
            item['book_href']='http:'+b.xpath('./div/div/div/div[@class="res-img"]/div/a/@href').extract_first()
            item['book_price']=b.xpath('./div/div/div/div[@class="res-info"]/p[1]/em').extract_first()
            item['book_content']=b.xpath('./div/div/div/div[2]/p[2]/a/text()').extract_first().strip()
            yield scrapy.Request(
                item['book_href'],
                callback=self.parse_book_detail,
                meta={"item":deepcopy(item)}

            )
        
    #解析图书详情页的数据
    def parse_book_detail(self,response):
        item=response.meta['item']
        #书详情页提取,价格,
        item['book_author']=response.xpath('//div[@id="proinfoMain"]/ul/li/text()').extract_first().strip()
        item['book_author']=re.sub('\n|\t|\r','',item['book_author'])
        item['book_pri']=re.findall('"itemPrice":"(.*?)",',response.body.decode(),re.S)
        item['book_pri']=item['book_pri'][0] if len(item['book_pri'])>0 else None
        print(item)

在传递item的时候重新开辟一块内存地址,这样就避免了数据的覆盖,那么我们重点放在翻页上,翻页是在图书的列表页进行的,所以我们提取到下一页的url,需要交给解析图书列表页的函数,即parse_book_list,重点就是item,我们还需要deepcopy吗??我们需要的item是没有修改过的,先理清思路,我们先从parse函数中获取到deepcopy(item),然后交给parse_book_list处理图书列表页并翻页,详情页传递deepcopy(item),然后交给parse_book_detail处理图书详情页。parse_book_list处理图书列表页还要翻页,注意是先提取列表页然后再翻页,翻页之后在解析列表页再翻页,那么我们翻页的时候这个item当然也是需要保证只存在大分类小分类的数据,我们可以在包含大分类小分类的item传递过来之后进行deeepcopy就可以了,代码参考:

# -*- coding: utf-8 -*-
import scrapy
import re
from copy import deepcopy
class SubookSpider(scrapy.Spider):
    name = 'subook'
    allowed_domains = ['book.suning.com','list.suning.com','product.suning.com',]
    start_urls = ['http://book.suning.com/']

    def parse(self, response):
        #xpath通常都是先抓大后抓小,先找到大的div标签,然后循环找小的div标签
        #1.大分类分组
        categary=response.xpath('//div[@class="left-menu-container"]/div[1]/div[@class="menu-item"]')
        for li in categary:
            item={}#定义一个字典,存储数据
            item['big_cate']=li.xpath('./dl/dt/h3/a/text()').extract_first()#提取大分类的文本
            #小分类分组
            a_list=li.xpath('./dl/dd')
            for a in  a_list:
               item['s_href']=a.xpath('./a/@href').extract_first()#提取分类图书列表页的url地址
               item['s_cate']=a.xpath('./a/text()').extract_first()#提取小分类的文本
               if item['s_href'] is not None:#判断url是否为空值
                    #构造解析分类图书列表页的解析方法,并传递item到对应的函数
                   yield scrapy.Request(
                       item['s_href'],
                       callback=self.parse_book_list,
                       meta={"item":deepcopy(item)}
                   )#key值可自定义,value就是item
    #解析分类图书列表页数据
    def parse_book_list(self,response):
        item=deepcopy(response.meta["item"])#首先获取item
        #图书列表页分组
        book_list=response.xpath('//div[@id="filter-results"]/ul/li')
        for b in book_list:
            #book_img存在src属性和src2属性,所以需要处理
            #book_price存在em标签下的
            #<b>¥</b>100<i>.00</i><i><i></i></i></em>
            #但是在html标签中是没有<b>¥</b>100<i>.00</i><i><i></i></i>的所以提取不到价格
            item['book_img']='http:'+b.xpath('./div/div/div/div[@class="res-img"]/div/a/img/@src|./div/div/div/div[@class="res-img"]/div/a/img/@src2').extract_first()
            #book图片的url地址不完整,这里补充一下
            item['book_href']='http:'+b.xpath('./div/div/div/div[@class="res-img"]/div/a/@href').extract_first()
            #item['book_price']=b.xpath('./div/div/div/div[@class="res-info"]/p[1]/em').extract_first()
            item['book_content']=b.xpath('./div/div/div/div[2]/p[2]/a/text()').extract_first().strip()
            yield scrapy.Request(
                item['book_href'],
                callback=self.parse_book_detail,
                meta={"item":deepcopy(item)}

            )
        #实现翻页

        next_url=response.xpath('//a[@id="nextPage"]/@href').extract_first()
        if next_url:
            yield scrapy.Request(
                'https://list.suning.com/'+next_url,
                callback=self.parse_book_list,
                meta={'item':response.meta['item']}
            )#每翻一页,还需要传递item数据
    #解析图书详情页的数据
    def parse_book_detail(self,response):
        item=response.meta['item']
        #书详情页提取,价格,
        item['book_author']=response.xpath('//div[@id="proinfoMain"]/ul/li/text()').extract_first().strip()
        item['book_author']=re.sub('\n|\t|\r','',item['book_author'])
        item['book_pri']=re.findall('"itemPrice":"(.*?)",',response.body.decode(),re.S)
        item['book_pri']=item['book_pri'][0] if len(item['book_pri'])>0 else None
        print(item)

我们需要注意在提取大分类小分类的时候,要提取的标签应该是下图:

Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据

它是隐藏状态的,所以我们不要提取错啦.爬虫完成之后大功告成了.

未经允许不得转载:作者:鳄鱼君, 转载或复制请以 超链接形式 并注明出处 鳄鱼君
原文地址:《Scrapy实战之爬取苏宁图书 实现翻页 爬取详情页数据》 发布于2020-03-08

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

评论 抢沙发

9 + 2 =


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

支付宝扫一扫打赏

微信扫一扫打赏

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

登录

忘记密码 ?

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

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

注册