用scrapy爬取京東的數據
本文目的是使用scrapy爬取京東上所有的手機數據,並將數據保存到MongoDB中。
一、項目介紹
主要目標
1、使用scrapy爬取京東上所有的手機數據
2、將爬取的數據存儲到MongoDB
環境
win7、python2、pycharm
技術
1、數據采集:scrapy
2、數據存儲:MongoDB
難點分析
和其他的電商網站相比,京東的搜索類爬取主要有以下幾個難點:
1、搜索一個商品時,一開始顯示的商品數量為30個,當下拉這一頁 時,又會出現30個商品,這就是60個商品了,前30個可以直接 從原網頁上拿到,後30個卻在另一個隱藏鏈接中,要訪問這兩個 鏈接,才能拿到一頁的所有數據。
2、隱藏鏈接的構造,發現最後的那個show_items字段其實是前30 個商品的id。
3、直接反問隱藏鏈接被拒絕訪問,京東的服務器會檢查鏈接的來源, 只有來自當前頁的鏈接他才會允許訪問。
4、前30個商品的那一頁的鏈接page字段的自增是1、3、5。。。這 樣的,而後30個的自增是2、4、6。。。這樣的。
下面看具體的分析。
二、網頁分析
首先打開京東的首頁搜索“手機”:
一開始他的地址是這樣的:
轉到第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爬取京東的數據