用scrapy爬取京东的数据

Posted twoice

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用scrapy爬取京东的数据相关的知识,希望对你有一定的参考价值。

本文目的是使用scrapy爬取京东上所有的手机数据,并将数据保存到MongoDB中。

 

一、项目介绍

 

主要目标

1、使用scrapy爬取京东上所有的手机数据

2、将爬取的数据存储到MongoDB

 

环境

win7python2pycharm

 

技术

1、数据采集:scrapy

2、数据存储:MongoDB

 

难点分析

和其他的电商网站相比,京东的搜索类爬取主要有以下几个难点:

1、搜索一个商品时,一开始显示的商品数量为30个,当下拉这一页 时,又会出现30个商品,这就是60个商品了,前30个可以直接 从原网页上拿到,后30个却在另一个隐藏链接中,要访问这两个 链接,才能拿到一页的所有数据。

2、隐藏链接的构造,发现最后的那个show_items字段其实是前30 个商品的id

3、直接反问隐藏链接被拒绝访问,京东的服务器会检查链接的来源, 只有来自当前页的链接他才会允许访问。

4、30个商品的那一页的链接page字段的自增是135。。。这 样的,而后30个的自增是246。。。这样的。

 

下面看具体的分析。

 

 

二、网页分析

首先打开京东的首页搜索“手机”:

 技术分享图片

 

一开始他的地址是这样的:技术分享图片

转到第2页,会看到,他的地址变成这样子了:

技术分享图片

 

后面的字段全变了,那么第2页的url明显更容易看出信息,主要修改的字段其实就是keyword,page,其实还有一个wq字段,这个得值和keyword是一样的。

那么我们就可以使用第二页的url来抓取数据,可以看出第2页的url中page字段为3。

 

但是查看原网页的时候却只有30条数据,还有30条数据隐藏在一个网页中:

 技术分享图片

 

从这里面可以看到他的Request url。

再看一下他的response:

 技术分享图片

里面正好就是我们需要的信息。

看一下他的参数请求:

技术分享图片

 

这些参数不难以构造,一些未知的参数可以删掉,而那个show_items参数,其实就是前30个商品的id:

技术分享图片

 

准确来说是data-pid

此时如果我们直接在浏览器上访问这个Request url,他会跳转到https://www.jd.com/?se=deny页面,并没有我们需要的信息,其实这个主要是请求头中的referer参数

 技术分享图片

这个参数就是在地址栏上的那个url,当然在爬取的时候我们还可以加个user-agent,那么分析完毕,我们开始敲代码。

 

 

三、爬取

创建一个scrapy爬虫项目:

scrapy startproject jdphone

 

生成一个爬虫:

scrapy genspider jd jd.com 

文件结构:

技术分享图片

 

items:  items.py

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


class JdphoneItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()  # 标题

    price = scrapy.Field()  # 价格

    comment_num = scrapy.Field()  # 评价条数

    url = scrapy.Field()  # 商品链接

    info = scrapy.Field()  # 详细信息

 

spiders:  jd.py

# -*- coding: utf-8 -*-
import scrapy
from ..items import JdphoneItem
import sys

reload(sys)
sys.setdefaultencoding("utf-8")


class JdSpider(scrapy.Spider):
    name = jd
    allowed_domains = [jd.com]  # 有的时候写个www.jd.com会导致search.jd.com无法爬取
    keyword = "手机"
    page = 1
    url = https://search.jd.com/Search?keyword=%s&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%s&cid2=653&cid3=655&page=%d&click=0
    next_url = https://search.jd.com/s_new.php?keyword=%s&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%s&cid2=653&cid3=655&page=%d&scrolling=y&show_items=%s

    def start_requests(self):
        yield scrapy.Request(self.url % (self.keyword, self.keyword, self.page), callback=self.parse)

    def parse(self, response):
        """
        爬取每页的前三十个商品,数据直接展示在原网页中
        :param response:
        :return:
        """
        ids = []
        for li in response.xpath(//*[@id="J_goodsList"]/ul/li):
            item = JdphoneItem()

            title = li.xpath(div/div/a/em/text()).extract()  # 标题
            price = li.xpath(div/div/strong/i/text()).extract()  # 价格
            comment_num = li.xpath(div/div/strong/a/text()).extract()  # 评价条数
            id = li.xpath(@data-pid).extract()  # id
            ids.append(‘‘.join(id))

            url = li.xpath(div/div[@class="p-name p-name-type-2"]/a/@href).extract()  # 需要跟进的链接

            item[title] = ‘‘.join(title)
            item[price] = ‘‘.join(price)
            item[comment_num] = ‘‘.join(comment_num)
            item[url] = ‘‘.join(url)

            if item[url].startswith(//):
                item[url] = https: + item[url]
            elif not item[url].startswith(https:):
                item[info] = None
                yield item
                continue

            yield scrapy.Request(item[url], callback=self.info_parse, meta={"item": item})

        headers = {referer: response.url}
        # 后三十页的链接访问会检查referer,referer是就是本页的实际链接
        # referer错误会跳转到:https://www.jd.com/?se=deny
        self.page += 1
        yield scrapy.Request(self.next_url % (self.keyword, self.keyword, self.page, ,.join(ids)),
                             callback=self.next_parse, headers=headers)

    def next_parse(self, response):
        """
        爬取每页的后三十个商品,数据展示在一个特殊链接中:url+id(这个id是前三十个商品的id)
        :param response:
        :return:
        """
        for li in response.xpath(//li[@class="gl-item"]):
            item = JdphoneItem()
            title = li.xpath(div/div/a/em/text()).extract()  # 标题
            price = li.xpath(div/div/strong/i/text()).extract()  # 价格
            comment_num = li.xpath(div/div/strong/a/text()).extract()  # 评价条数
            url = li.xpath(div/div[@class="p-name p-name-type-2"]/a/@href).extract()  # 需要跟进的链接

            item[title] = ‘‘.join(title)
            item[price] = ‘‘.join(price)
            item[comment_num] = ‘‘.join(comment_num)
            item[url] = ‘‘.join(url)

            if item[url].startswith(//):
                item[url] = https: + item[url]
            elif not item[url].startswith(https:):
                item[info] = None
                yield item
                continue

            yield scrapy.Request(item[url], callback=self.info_parse, meta={"item": item})

        if self.page < 200:
            self.page += 1
            yield scrapy.Request(self.url % (self.keyword, self.keyword, self.page), callback=self.parse)

    def info_parse(self, response):
        """
        链接跟进,爬取每件商品的详细信息,所有的信息都保存在item的一个子字段info中
        :param response:
        :return:
        """
        item = response.meta[item]
        item[info] = {}
        type = response.xpath(//div[@class="inner border"]/div[@class="head"]/a/text()).extract()
        name = response.xpath(//div[@class="item ellipsis"]/text()).extract()
        item[info][type] = ‘‘.join(type)
        item[info][name] = ‘‘.join(name)

        for div in response.xpath(//div[@class="Ptable"]/div[@class="Ptable-item"]):
            h3 = ‘‘.join(div.xpath(h3/text()).extract())
            if h3 == ‘‘:
                h3 = "未知"
            dt = div.xpath(dl/dt/text()).extract()
            dd = div.xpath(dl/dd[not(@class)]/text()).extract()
            item[info][h3] = {}
            for t, d in zip(dt, dd):
                item[info][h3][t] = d
        yield item

 

item pipeline:  pipelines.py

 

# -*- coding: utf-8 -*-
from scrapy.conf import settings
from pymongo import MongoClient


class JdphonePipeline(object):
    def __init__(self):
        # 获取setting中主机名,端口号和集合名
        host = settings[MONGODB_HOST]
        port = settings[MONGODB_PORT]
        dbname = settings[MONGODB_DBNAME]
        col = settings[MONGODB_COL]

        # 创建一个mongo实例
        client = MongoClient(host=host,port=port)

        # 访问数据库
        db = client[dbname]

        # 访问集合
        self.col = db[col]

    def process_item(self, item, spider):
        data = dict(item)
        self.col.insert(data)
        return item

 

 

setting:  setting.py

 

# -*- coding: utf-8 -*-
BOT_NAME = jdphone

SPIDER_MODULES = [jdphone.spiders]
NEWSPIDER_MODULE = jdphone.spiders


# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/67.0.3396.62 Safari/537.36

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

# 主机环回地址
MONGODB_HOST = 127.0.0.1
# 端口号,默认27017
MONGODB_POST = 27017
# 设置数据库名称
MONGODB_DBNAME = JingDong
# 设置集合名称
MONGODB_COL = JingDongPhone

ITEM_PIPELINES = {
   jdphone.pipelines.JdphonePipeline: 300,
}

 

 

其他的文件都不做改变。

运行爬虫:

scrapy crawl jd

等待几分钟后,数据都存储到了MongoDB中了,现在来看一看MongoDB中的数据。

 

 

四、检查数据

在命令行中开启mongo:

技术分享图片

 

看一下数据库:

 技术分享图片

发现JingDong中有5M数据。

 

看一下具体状态:

 技术分享图片

硬盘上的数据大小为4720KB,共4902条数据

 

最后来看一下数据:

技术分享图片

技术分享图片

 

数据保存成功!

 

以上是关于用scrapy爬取京东的数据的主要内容,如果未能解决你的问题,请参考以下文章

爬虫(十七):Scrapy框架 对接selenium爬取京东商品数据

用scrapy爬取京东商城的商品信息

scrapy爬取京东iPhone11评论

scrapy爬取京东

scrapy按顺序启动多个爬虫代码片段(python3)

scrapy主动退出爬虫的代码片段(python3)