天天看點

Scrapy + Selenium 爬取京東商品清單

爬取思路架構:

  1. 先建立一個scrapy項目
  2. 編寫items檔案
  3. 建立爬蟲
  4. 修改middlewares
  5. 修改pipelines
  6. 配置settings
  7. 運作Scrapy

直接進入正題:

1、先建立一個scrapy項目

在系統指令行輸入:

scrapy startproject jd
           
  • 項目建立成功後,會出現下圖所示檔案。
    Scrapy + Selenium 爬取京東商品清單
    各個主要檔案的作用:

scrapy.cfg :項目的配置檔案

jd/ :項目的Python子產品,将會從這裡引用代碼

jd/items.py :項目的目标檔案

jd/pipelines.py :項目的管道檔案

jd/settings.py :項目的設定檔案

jd/spiders/ :存儲爬蟲代碼目錄

2、編寫items檔案

我們這裡主要爬取商品清單裡的商品名稱、價格、店鋪、評論條數、商品詳情的url和商品的提供商是否為自營,代碼如下:

import scrapy
class JdItem(scrapy.Item):
    # define the fields for your item here like:
    #名字
    name = scrapy.Field()
    #價格
    price = scrapy.Field()
    #店鋪
    store = scrapy.Field()
    #評論條數
    evaluate_num = scrapy.Field()
    #商品url
    detail_url = scrapy.Field()
    #提供商
    support = scrapy.Field()
           

3、items檔案寫完之後,就要制作我們的爬蟲啦,在系統指令行中目前目錄下輸入指令,将在jd/spider目錄下建立一個名為jingdong的爬蟲,并指定爬取域的範圍,代碼如下:

  • 打開 jd/spider目錄裡的 jingdong.py,要建立一個Spider, 你必須用scrapy.Spider類建立一個子類,并确定了三個強制的屬性 和 一個方法。

name = “” :這個爬蟲的識别名稱,必須是唯一的,在不同的爬蟲必須定義不同的名字。

allow_domains = [] 是搜尋的域名範圍,也就是爬蟲的限制區域,規定爬蟲隻爬取這個域名下的網頁,不存在的URL會被忽略。

start_urls = () :爬取的URL元祖/清單。爬蟲從這裡開始抓取資料,是以,第一次下載下傳的資料将會從這些urls開始。其他子URL将會從這些起始URL中繼承性生成。

parse(self, response) :解析的方法,每個初始URL完成下載下傳後将被調用,調用的時候傳入從每一個URL傳回的Response對象來作為唯一參數,主要作用如下:

負責解析傳回的網頁資料(response.body),提取結構化資料(生成item)

生成需要下一頁的URL請求。

- 了解了各自的使用方法後,修改代碼為以下:

# -*- coding: utf-8 -*-
import scrapy
from jd.items import JdItem
class JingdongSpider(scrapy.Spider):
    name = "jingdong"
    allowed_domains = ["search.jd.com"]
    #這裡我爬取的是手機,可根據要爬取的不同商品,修改關鍵詞
    base_url = 'https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%E6%89%8B%E6%9C%BA&cid2=653&cid3=655&psort=4&click=0' 
    page = 
    start_urls = [base_url + '&page=' + str(page) + '&click=0']
    def start_requests(self):   
        yield scrapy.Request(url = self.base_url,callback=self.parse,meta={'page':self.page},dont_filter=True)
    def parse(self,response):
        #商品清單,在網頁源碼上用xpath盡情解析吧,每個item都加上try,except使程式更加強壯
        products = response.xpath('//ul[@class="gl-warp clearfix"]/li')
        #清單疊代
        for product in products:
            item = JdItem()
            try:
                name = ''.join(product.xpath('.//div[@class="p-name p-name-type-2"]/a/em/text()').extract()).strip().replace(' ','')
            except:
                name = ''
            try:
                price = product.xpath('.//div[@class="p-price"]//i/text()').extract()[]
            except:
                price = ''

            try:
                store = product.xpath('.//div[@class="p-shop"]//a/@title').extract()[]
            except:
                store = ''
            try:
                evaluate_num = product.xpath('.//div[@class="p-commit"]/strong/a/text()').extract()[]
            except:
                evaluate_num = ''
            try:
                detail_url = product.xpath('.//div[@class="p-name p-name-type-2"]/a/@href').extract()[]
            except:
                detail_url = ''
            try:
                if product.xpath('.//div[@class="p-icons"]/i/text()').extract()[]=='自營':
                    support = '自營'
                else:
                    support = '非自營'
            except:
                support = '非自營'
            item['name'] = name 
            item['price'] = price
            item['store'] = store
            item['evaluate_num'] = evaluate_num
            item['detail_url'] = detail_url
            item['support'] = support
            #這裡的yield将資料交給pipelines
            yield item
            print(item)
#這裡的目的是配合middlewares中的slenium配合,這裡每次都要打開相同的網頁self.base_url,然後運用selenium操作浏覽器,在最下方的頁碼中輸入要查詢的頁數,我們這裡查詢1-100頁
        if self.page < :
            self.page += 
            print(self.page)
#這裡的meta使用來傳遞page參數,dont_filter表示不去重,因為scrapy預設會去重url,我們每次請求的網頁都是重複的,是以這裡不去重           
            yield scrapy.Request(url=self.base_url,callback=self.parse,meta={'page':self.page},dont_filter=True)
           

4、修改middlewares,把請求傳送給middlewares的selenium,由selenium發送請求,并将Response傳給jingdong.py的parse函數解析。代碼如下:

# -*- coding: utf-8 -*-

# Define here the models for your spider middleware
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/spider-middleware.html

from scrapy import signals
#導入 webdriver
from selenium import webdriver
from selenium.webdriver.common.by import By
#WebDriverWait庫,負責循環等待
from selenium.webdriver.support.ui import WebDriverWait
#excepted_conditions類,負責條件出發
from selenium.webdriver.support import expected_conditions as EC
#
from scrapy.http import HtmlResponse
from scrapy.conf import settings
import random
import time

#随機使用user_agent,user_agents從settings中讀取
class JdDownloadmiddlewareRandomUseragent(object):
    def __init__(self):
        self.useragents = settings['USER_AGENTS']
    def process_request(self,request,spider):
        useragent = random.choice(self.useragents)
        print(useragent)
        request.headers.setdefault('User-Agent',useragent)
#下載下傳中間件,用selenium爬取,這裡我用的是Firefox的Webdriver,另外還帶了Chromdriver的使用代碼
class JdSpiderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the spider middleware does not modify the
    # passed objects.

    def __init__(self):
        print('打開了火狐浏覽器')
        #firefox浏覽器
        firefox_profile = webdriver.FirefoxProfile()
        fireFoxOptions = webdriver.FirefoxOptions()
        #設定無圖打開
        firefox_profile.set_preference('permissions.default.image', )
        #設定不加載Flash
       firefox_profile.set_preference('dom.ipc.plugins.enabled.libflashplayer.so', 'False')
        #設定悟透浏覽器
        fireFoxOptions.set_headless()
        fireFoxOptions.add_argument(')
        #timeout表示網頁請求逾時
        self.browser = webdriver.Firefox(firefox_options=fireFoxOptions,firefox_profile=firefox_profile,timeout=)
        self.wait = WebDriverWait(self.browser,timeout=)
         '''
        #Chrome浏覽器
        options = webdriver.ChromeOptions()
        #設定中文
        #options.add_argument(')
        #設定無圖加載 1 允許所有圖檔; 2 阻止所有圖檔; 3 阻止第三方伺服器圖檔
        prefs = {
            'profile.default_content_setting_values':{
            'images': 2
            }
        }
        options.add_experimental_option('prefs',prefs)
        #設定無頭浏覽器
        options.add_argument('--headless')
        self.browser = webdriver.Chrome(chrome_options=options)
        #設定等待請求網頁時間最大為self.timeout
        self.wait = WebDriverWait(self.browser,self.timeout)
        self.browser.set_page_load_time(self.timeout)
        '''

    def __del__(self):
        print('關閉Firefox')
        #爬蟲結束後,關閉浏覽器
        self.browser.close()   


    def process_request(self,request,spider):
        page = request.meta.get('page',)
        try:
            print('Selenium啟動解析')
            self.browser.get(request.url)
            #滾動條下拉到底
            self.browser.execute_script("document.documentElement.scrollTop=10000")
            #等待網頁加載完畢
            time.sleep()
            #如果傳過來的page不是第一頁就需要在最下面的輸入頁碼處,輸入page,并按确定鍵跳轉到指定頁面
            if page > :
                input = self.wait.until(EC.presence_of_element_located((By.XPATH,'.//span[@class="p-skip"]/input'))) # 擷取輸入頁面數框
                submit = self.wait.until(EC.element_to_be_clickable((By.XPATH,'.//span[@class="p-skip"]/a')))  # 擷取确定按鈕
                input.clear()
                input.send_keys(page)
                submit.click()
                #滾動條下拉到底,第二種寫法       
                self.browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                time.sleep()
            # 如果當 str(page),即目前頁碼出現在高亮文本的時候,就代表頁面成功跳轉
            self.wait.until(
                EC.text_to_be_present_in_element((By.XPATH,'.//span[@class="p-num"]/a[@class="curr"]'),str(page)))

            # 等待加載完所有的商品list 然後進一步解析
            self.wait.until(EC.element_to_be_clickable((By.XPATH,'.//span[@class="p-skip"]/a')))
            #self.wait.until(EC.presence_of_element_located((By.XPATH,'.//ul[@class="gl-warp clearfix"]/li')))
            time.sleep()
            body = self.browser.page_source
            print('selenium開始通路第'+str(page)+'頁')
           #将selenium得到的網頁資料傳回給parse解析
            return HtmlResponse(url=request.url,body=body,encoding='utf-8',request=request)

        except Exception as E:
            print(str(E))
            return HtmlResponse(url =request.url,status=,request=request)
           

5、修改pipelines,jingdong.py中parse函數中的yield item的資料給到了pipelines,将資料存入到Mongodb資料庫,代碼如下:

# -*- coding: utf-8 -*-

import pymongo
class JdPipeline(object):
    def __init__(self):
        self.client = pymongo.MongoClient('localhost',)
        scrapy_db = self.client['jd']       # 建立資料庫
        self.coll = scrapy_db['scrapyphone']      # 建立資料庫中的表格

    def process_item(self, item, spider):
        self.coll.insert_one(dict(item))
        return item

    def close_spider(self, spider):
        self.client.close()
           

6、配置settings,為減少篇幅,隻把修改的地方放了上來,其他的預設就可以

BOT_NAME = 'jd'
SPIDER_MODULES = ['jd.spiders']
NEWSPIDER_MODULE = 'jd.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'jd (+http://www.yourdomain.com)'
#USER_AGENTS清單
USER_AGENTS = [ 
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", 
    "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", 
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", 
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", 
    "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", 
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", 
    "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", 
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", 
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", 
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", 
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", 
    "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", 
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.132 Safari/537.36",  
    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0"
]
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
#下載下傳延時,一般設定2.5左右,防止被封哦
DOWNLOAD_DELAY = 
#禁止cookies
# Disable cookies (enabled by default)
COOKIES_ENABLED = False
#設定下載下傳中間件,把我們自己寫的加進去,後面的數字越小,代表執行優先級越高
# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
DOWNLOADER_MIDDLEWARES = {
   'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':None,
   'jd.middlewares.JdDownloadmiddlewareRandomUseragent':,
   'jd.middlewares.JdSpiderMiddleware': 

}

# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
#PIPELINES把我們自己寫的存儲類加上
ITEM_PIPELINES = {
   'jd.pipelines.JdPipeline': ,
}
           

7、運作程式

在系統指令行中的jd目錄下,執行代碼:

scrapy crawl jingdong
           

代碼就跑起來了,大功告成,100頁,每頁60個,共爬取6000條商品:

Scrapy + Selenium 爬取京東商品清單

存入Mongodb資料庫,檢視如下圖:

Scrapy + Selenium 爬取京東商品清單
  • 由于京東商品清單頁為ajax請求,正常的請求隻能拿到一半的資料,另一半資料需要下拉滾動條才會顯示,是以我們用selenium模拟浏覽器下拉操作通路網頁,才能得到完整的資料。雖然用的selenium,但是爬取速度也還算可以,不知道您看完之後,是否學會了使用Scrapy + Selenium爬取網頁呢,如果有不懂的地方,可以在下方留言,一起進步!

    需要源碼的可去Github下載下傳,歡迎star和提出問題:

    https://github.com/wangyeqiang/Craw

最後希望,早日學會Scrapy + Selenium。