一塊兒學爬蟲——使用selenium和pyquery爬取京東商品列表


layout: article
title: 一塊兒學爬蟲——使用selenium和pyquery爬取京東商品列表
mathjax: true
---css

今天一塊兒學起使用selenium和pyquery爬取京東的商品列表。本文的全部代碼是在pycharm IDE中完成的,操做系統window 10。html

一、準備工做
安裝pyquery和selenium類庫。依次點擊file->settings,會彈出以下的界面:
selenuim pyquery
而後依次點擊:project->project Interpreter->"+",,如上圖的紅色框所示。而後會彈出下面的界面:
selenuim pyquery
輸入selenium,在結果列表中選中「selenium」,點擊「install package」按鈕安裝selenium類庫。pyquery也是同樣的安裝方法。python

安裝chrome和chrome driver插件。chrome dirver插件下載地址:http://npm.taobao.org/mirrors/chromedriver/。 切記chrome和chrome dirver的版本要一致。個人chrome版本是70,對應chrome driver是2.44,2.43,2.42。web

下載chrome driver解壓後,將exe文件拷貝到pycharm開發空間的Scripts文件夾中:
selenuim pyquerychrome

二、分析要爬取的頁面
此次是爬取京東圖書中計算機書籍類書籍的信息。
打開chrome,打開開發者工具,輸入www.jd.com,分析查詢輸入框和查詢按鈕的css代碼:
selenuim pyquery
經過分析發現,搜索框的css代碼是id=「key」,查詢按鈕的css代碼是class=「button」。下面是使用selenium調用chrome瀏覽器在搜索框輸入關鍵詞「計算機書籍」並點擊查詢按鈕出發查詢請求的代碼:npm

from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from pyquery import PyQuery as pq

#經過Chrome()方法打開chrome瀏覽器
browser = webdriver.Chrome()
#訪問京東網站
browser.get("https://www.jd.com")
#等待50秒
wait = WebDriverWait(browser, 50)
#經過css選擇器的id屬性得到輸入框
input = browser.find_element_by_id('key')
#在輸入框中寫入要查詢的信息
input.send_keys('計算機書籍')
#獲取查詢按鈕
submit_button = browser.find_element_by_class_name('button')
#點擊查詢按鈕
submit_button.click()

上面代碼成功啓動chrome瀏覽器,自動完成在搜索框中輸入關鍵詞並點擊查詢按鈕的操做。瀏覽器

點擊完查詢按鈕以後,會加載出符合條件的書籍,以下圖所示:
selenuim pyquery工具

鼠標往下滾動到達網頁底部時,會看到分頁的界面:
selenuim pyquery網站

下一步要作的工做就是分析商品列表頁和分頁的css代碼。ui

咱們要爬去圖書的書名、圖片、價格、出版社、評價數量信息。下圖是商品列表也的界面,
selenuim pyquery
經過開發者工具可知class="gl-item"的li節點是一條商品的信息,上圖這個的紅色框。
綠色框是商品的圖片信息。對應的是class=「p-img」的div節點。
藍色框是商品的價格信息,對應的是class="p-price"的div節點。
黑色框是商品的名稱信息,對應的是class="p-name"的div節點。
紫色狂是商品的評價信息,對應的是class="p-commit"的div節點。
褐色框是商品的出版社信息,對應的是class=「p-shopnum」的div節點。

咱們使用pyquery解析商品的信息,使用selenium打開一個頁面時,經過page_source屬性就能夠獲得頁面的源碼。
這裏有個坑須要注意:京東的商品列表頁是顯示固定數量的商品,當加載新的商品頁的時候,並非一會兒把該頁的商品都加載出來,而是鼠標向下滾動時纔會動態加載新的商品。所以咱們在使用selenium時,要將鼠標設置自動滾動到商品列表頁的底部,這樣就會把該頁的全部商品都顯示出現,爬取的數據才能完整,不然會出現丟失。

下面給出解析一個商品的代碼:

from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from pyquery import PyQuery as pq
import time

#經過Chrome()方法打開chrome瀏覽器
browser = webdriver.Chrome()
#訪問京東網站
browser.get("https://www.jd.com")
#等待50秒
wait = WebDriverWait(browser, 50)
#經過css選擇器的id屬性得到輸入框
input = browser.find_element_by_id('key')
#在輸入框中寫入要查詢的信息
input.send_keys('計算機書籍')
#獲取查詢按鈕
submit_button = browser.find_element_by_class_name('button')
#點擊查詢按鈕
submit_button.click()

# 模擬下滑到底部操做
for i in range(1, 5):
    browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(1)

#商品列表的總頁數
total = wait.until(
    EC.presence_of_all_elements_located(
        (By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > em:nth-child(1) > b')
    )
)

html = browser.page_source.replace('xmlns', 'another_attr')

doc = pq(html)
#一個商品信息是存放在class=「gl-item」的li節點內,items()方法是獲取全部的商品列表。
li_list = doc('.gl-item').items()
#循環解析每一個商品的信息
for item in li_list:
    image_html = item('.gl-i-wrap .p-img')
    book_img_url = item.find('img').attr('data-lazy-img')
    if book_img_url == "done":
        book_img_url = item.find('img').attr('src')
    print('圖片地址:' + book_img_url)
    item('.p-name').find('font').remove()
    book_name = item('.p-name').find('em').text()
    print('書名:' + book_name)
    price = item('.p-price').find('em').text() + str(item('.p-price').find('i').text())
    print('價格:' + price)
    commit = item('.p-commit').find('strong').text()
    print('評價數量:' + commit)
    shopnum  = item('.p-shopnum').find('a').text()
    print('出版社:' + shopnum)
    print('++++++++++++++++++++++++++++++++++++++++++++')

對於有分頁的狀況,須要一頁一頁的解析商品,咱們能夠經過selenium調用「下一頁」按鈕來獲取下一頁的源代碼。咱們來分析下一頁的css代碼,滾動鼠標到網頁的底部,會看到分頁的狀況:
selenuim pyquery

經過上圖可知,須要獲取到「下一頁」按鈕,而後調用click方法。相應的代碼爲:

next_page_button = wait.until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_bottomPage > span.p-num > a.pn-next > em'))
    )
    next_page_button.click()

    #滑動到頁面底部,用於加載數據
    for i in range(0,3):
        browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(10)

    #一頁顯示60個商品,"#J_goodsList > ul > li:nth-child(60)確保60個商品都正常加載出來。
    wait.until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, "#J_goodsList > ul > li:nth-child(60)"))
    )
    # 判斷翻頁成功,當底部的分頁界面上顯示第幾頁時,就顯示翻頁成功。
    wait.until(
        EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#J_bottomPage > span.p-num > a.curr"), str(page_num))
    )

下面給出完整代碼:

from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from pyquery import PyQuery as pq
import time

#打開不一樣的瀏覽器實例
def openBrower(brower_type):
    if brower_type == 'chrome':
        return webdriver.Chrome()
    elif brower_type == 'firefox':
        return webdriver.Firefox()
    elif brower_type == 'safari':
        return webdriver.Safari()
    elif brower_type == 'PhantomJS':
        return webdriver.PhantomJS()
    else :
        return webdriver.Ie()

def parse_website():
    # 經過Chrome()方法打開chrome瀏覽器
    browser = openBrower('chrome')
    # 訪問京東網站
    browser.get("https://www.jd.com")
    # 等待50秒
    wait = WebDriverWait(browser, 50)
    # 經過css選擇器的id屬性得到輸入框。until方法表示瀏覽器徹底加載到對應的節點,才返回相應的對象。presence_of_all_elements_located是經過css選擇器加載節點
    input = wait.until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, '#key'))
    )

    # input = browser.find_element_by_id('key')
    # 在輸入框中寫入要查詢的信息
    input[0].send_keys('計算機書籍')
    # 查詢按鈕徹底加載完畢,返回查詢按鈕對象
    submit_button = wait.until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '.button'))
    )
    # 點擊查詢按鈕
    submit_button.click()

    # 模擬下滑到底部操做
    for i in range(0,3):
        browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(3)

    # 商品列表的總頁數
    total = wait.until(
        EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > em:nth-child(1) > b')
        )
    )
    html = browser.page_source.replace('xmlns', 'another_attr')
    parse_book(1,html)

    for page_num in range(2,int(total[0].text) + 1):
        parse_next_page(page_num,browser,wait)

##解析下一頁
def parse_next_page(page_num,browser,wait):

    next_page_button = wait.until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_bottomPage > span.p-num > a.pn-next > em'))
    )
    next_page_button.click()

    #滑動到頁面底部,用於加載數據
    for i in range(0,3):
        browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(10)

    #一頁顯示60個商品,"#J_goodsList > ul > li:nth-child(60)確保60個商品都正常加載出來。
    wait.until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, "#J_goodsList > ul > li:nth-child(60)"))
    )
    # 判斷翻頁成功,當底部的分頁界面上顯示第幾頁時,就顯示翻頁成功。
    wait.until(
        EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#J_bottomPage > span.p-num > a.curr"), str(page_num))
    )

    html = browser.page_source.replace('xmlns', 'another_attr')
    parse_book(page_num, html)

def parse_book(page,html):
    doc = pq(html)
    li_list = doc('.gl-item').items()
    print('-------------------第' + str(page) + '頁的圖書信息---------------------')
    for item in li_list:
        image_html = item('.gl-i-wrap .p-img')
        book_img_url = item.find('img').attr('data-lazy-img')
        if book_img_url == "done":
            book_img_url = item.find('img').attr('src')
        print('圖片地址:' + book_img_url)
        item('.p-name').find('font').remove()
        book_name = item('.p-name').find('em').text()
        print('書名:' + book_name)
        price = item('.p-price').find('em').text() + str(item('.p-price').find('i').text())
        print('價格:' + price)
        commit = item('.p-commit').find('strong').text()
        print('評價數量:' + commit)
        shopnum = item('.p-shopnum').find('a').text()
        print('出版社:' + shopnum)
        print('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++')

def main():
    parse_website()
if __name__ == "__main__":
    main()

三、總結 (1)要記得調用 browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")方法模擬鼠標向下滾動的操做加載數據,不然數據會不完整。 (2)在經過page_source獲取網頁源碼時,若是有xmlns命名空間,則要將該命名空間空其餘的字段代替,不然使用pyquery解析網頁時,會解析不出數據。pyquery解析xmlns命名空間時,會自動隱藏掉某些屬性。致使沒法征程解析網頁,緣由不詳,若是有人知道緣由請告知。 (3)儘可能用wait.until(EC.presence_of_all_elements_located())方法,這樣能夠避免網頁沒法正常加載而提早返回網頁信息的狀況。保證數據的準確。

相關文章
相關標籤/搜索