用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條數據

 

最后來看一下數據:

 

數據保存成功!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM