【一塊兒學爬蟲】爬蟲實戰:爬取京東零食

簡介

使用Selenium+chrome/PhantomJS爬取京東零食。 京東的頁面比較複雜:含有各類請求參數、加密參數,若是直接請求或者分享Ajax的話會很是的繁瑣,Selenium是一個自動化測試工具,能夠驅動瀏覽器完成各類操做:模擬點擊、輸入、下滑等各類功能,如此一來,咱們只須要關心操做,而不須要關心後臺發生了什麼樣的請求。PhantomJS是無界面的瀏覽器,比Selenium方便,phantomJS已經被廢棄了,直接配置Selenium的瀏覽器options就能夠實現無界面瀏覽器(詳情請看文末的實現代碼)。css

本次實戰爬取的是京東搜索「零食」出現的全部內容,解析庫是PyQuery。 ###目標站點分析 分析發現京東搜索「零食」後發起了不少請求,比較複雜。這裏使用Selenium驅動瀏覽器,這能夠屏蔽這些複雜請求的分析,因此須要依次實現:html

  • 模擬京東搜索關鍵詞「美食」
  • 模擬鼠標點擊
  • 獲取首頁的內容
  • 模擬點擊翻頁或者經過模擬輸入翻頁
  • 網頁解析

Selenium和webdriver安裝可看下以前的文章 (1)實現步驟 java

瀏覽器右鍵->檢查,上圖中的copy選項卡能夠在元素查找時使用,這篇文章中咱們使用css選擇器查找元素,點擊「copy selector」,在查找元素時直接粘貼就能夠了。 在正式寫代碼前須要考慮到:python

  • 咱們必需要等待輸入框和搜索按鈕加載完,才能進行輸入和搜索操做,這就意味着咱們在實現代碼中須要加一些等待條件,這個能夠上Selenium官網上查看:主要是wait_until函數。
  • 加載完後須要獲取總頁數等一些信息
  • 頁面切換:能夠點擊下一頁;也能夠經過輸入框輸入頁碼
  • 頁面切換以後,經過高亮的頁碼是否加載完成來判斷翻頁是否完成
  • 完成等待頁面加載完成和翻頁等之類框架的功能後,咱們再考慮細節,即網頁內容解析

(2)網頁解析 web

上圖能夠和清楚的發現,全部頁面商品都是以li的形式組織的,頁面解析思路也就很明確了。 應該注意的是,在進行網頁源碼解析以前,咱們須要先判斷網頁是否加載完成了。確認網頁加載完後,咱們獲取網頁的源代碼,使用PyQuery進行解析(也可使用正則表達式,上一次實戰使用的是正則,此次就用PyQuery解析)。

(3)在真實貼代碼和數據以前,先說明幾個曾經踩過的坑:面試

  • 肯定要爬取的數據:肯定目標
  • 寫代碼前 必定要分析完網頁源碼
  • 注意細節:有些網頁數據要等待必定時間後纔會加載出來,網頁源碼中包括不少數據,其中大部分數據是經過js請求獲取後纔會填充到網頁源碼中,因此有些數據加載的比較慢,因此獲取數據前必定要等待數據加載完
  • 本次爬取的是京東搜索「零食」關鍵詞後按照評論數量進行排序的數據:商品名字、價格、總評論數、好評數、中評數、差評數、好評率等數據,分析後發現:後面四個數據,必須點擊商品詳情後才能夠獲取到,可是不一樣商品的詳情頁面有些許不一樣,下面是兩種典型的商品詳情頁面: 1.五個導航欄
    2.三個導航欄
    顯然上面兩類商品的詳情頁面 不管是在五個導航欄仍是三個,咱們都須要點擊「商品評價」按鈕來獲取上述後四個數據。可是兩類頁面明顯存在差異,若是頁面種類不少,能夠考慮換種實現方式,若是頁面種類很少,能夠採用分類的方式。在本次實現中因爲只存在兩種頁面,咱們採用的是分類的方式。
  • 有些數據不會加載出來,必須下拉頁面後那部分數據纔會加載出來,這是下拉頁面就很重要,不然會出現找到不到元素的異常
  • 爲了加快網頁的訪問速度,能夠進行必要的瀏覽器配置:好比,訪問時不加載圖片,和使用無界面瀏覽器等

源碼和數據:

下面是爬取的數據: 正則表達式

源碼 下面是本文的核心代碼,掃描下方二維碼,發送關鍵詞「 京東」便可獲取本文的完整源碼和詳細程序註釋
掃碼關注,及時獲取更多精彩內容。(博主今日頭條大數據工程師)
公衆號專一: 互聯網求職面經javapython爬蟲大數據等技術、海量 資料分享:公衆號後臺回覆「csdn文庫下載」便可免費領取【csdn】和【百度文庫】下載服務;公衆號後臺回覆「 資料」:便可領取 5T精品學習資料java面試考點java面經總結,以及 幾十個java、大數據項目資料很全,你想找的幾乎都有

完整源碼和代碼詳細解析chrome

# 京東首頁搜素框
def search(key_words='零食'):
    try:
        input_text = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#key')))
        button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#search > div > div.form > button')))
        input_text.send_keys(key_words)
        button.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#J_topPage > span > i')))
        total = int(total.text)
        order = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_filter > div.f-line.top > div.f-sort > a:nth-child(3)')))
        order.click()
    except TimeoutException:
        print('search 超時')

    print('一共', total, '頁')
    return total



# 翻頁

def next_page(next_page):
    # 翻頁使用的策略是手動輸入頁碼,而後點擊跳轉,可是有個問題就是:
    # 不滑動滑塊直接定位頁碼輸入框會出現:找不到該元素的異常,這主要是,頁面未加載形成的
    # 也就是:當你在京東上搜索商品的時候,瀏覽器沒有下拉到翻頁那裏時,頁面就不會加載,頁面沒有加載天然找不到對應的元素
    # 這也是下面這三條指令的意義所在
    # browser.execute_script(js)
    try:
        browser.execute_script('arguments[0].scrollIntoView()',
                               browser.find_element_by_css_selector('#footer-2017 > div > div.copyright_info > p:nth-child(1) > a:nth-child(1)'))
        time.sleep(2)
        # 下滑直到「下一頁」按鈕出現
        browser.execute_script('arguments[0].scrollIntoView()',
                               browser.find_element_by_css_selector('#J_bottomPage > span.p-num > a.pn-next'))
        input_text = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > input')))
        jump = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > a')))
    except TimeoutException:
        print('翻頁超時')

    input_text.clear()
    input_text.send_keys(next_page)
    jump.click()
    try:
        # 等待翻頁完成
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#J_bottomPage > span.p-num > a.curr'), str(next_page)))
        browser.execute_script('arguments[0].scrollIntoView()',
                               browser.find_element_by_css_selector('#J_bottomPage > span.p-num > a.pn-next'))
    except TimeoutException:
        print('翻頁失敗', next_page)

    print('\n')
    print('解析第', next_page, '頁商品')
    try:
        for good in parse_page():
            write_to_csv_file(good.values())
    except TimeoutException:
        print('第', next_page, '頁的第', count_item, '個商品解析時發生超時')
        browser.switch_to.window(browser.window_handles[0])
        return None

# 頁面數據解析
def parse_page():
    global count_item
    count_item = 0# 統計每一頁的商品
    # 解析頁面前,先將瀏覽器頁面下滑置「下一頁」地方
    browser.execute_script('arguments[0].scrollIntoView()',
                           browser.find_element_by_css_selector('#J_bottomPage > span.p-num > a.pn-next'))
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-list .ml-wrap #J_goodsList .gl-item')))
    doc = pq(browser.page_source, parser="html")
    goods = doc('.m-list .ml-wrap #J_goodsList .gl-item').items()

    browser.switch_to.window(browser.window_handles[1])
    for good in goods:
        count_item = count_item +1
        print('當前是第',count_item,'個商品')
        detail_herf =  good.find('.p-name a').attr('href')
        browser.get('http://'+detail_herf)
        # 等待商品介紹 評價等數據加載完成
        browser.execute_script('arguments[0].scrollIntoView()',
                               browser.find_element_by_css_selector('.detail #detail .tab-main ul li'))
        detail_doc = pq(browser.page_source,parser='html')
        # 京東上主要有兩類商品:
        # 一類:與商品介紹並列的一共5個標籤
        # 第二類:只有商品介紹和評價兩類標籤,
        # 下面這個css定位很容易寫錯
        ul = list(detail_doc('.detail #detail .tab-main ul li').items())
        if len(ul)==5 :
            # 商品評價按鈕 五個li標籤,對應第二類商品
            #等待評價按鈕加載完成
            comments_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'#detail > div.tab-main.large.detail-elevator > ul > li:nth-child(2)')))
            comments_button.click()  # 點擊「評價」按鈕

            rate = wait.until(EC.presence_of_element_located(
                (By.CSS_SELECTOR, '#i-comment > div.rate > strong')))# 等待rate加載完成
            rate_comments = rate.text  # rate是webElement類型,調用text便可獲取對應的文本,注意不是調用text()

            # 等待差評數量加載完成(差評數量加載完了也就意味着好評和中評數量加載完了)
            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#comments-list > div.mt > div > ul > li:nth-child(4) > a > em')))
            tmp_doc = pq(browser.page_source, parser='html')
            comments_obtain = tmp_doc('#comments-list .mt-inner ul li').items()# 獲取評論數量
            index = 0
            for comment in comments_obtain:
                if index == 1:
                    good_comments = comment('a em').text()[1:-1]
                elif index == 2:
                    neutral_comments = comment('a em').text()[1:-1]
                elif index == 3:
                    bad_comments = comment('a em').text()[1:-1]
                    break
                index = index + 1
            yield {
                'name': good.find('.p-name a em').text().strip().replace('\n', ' '),
                'price': good.find('.p-price i').text(),
                'total_comments': good.find('.p-commit a').text(),
                'good_comments': good_comments,
                'neutral_comments': neutral_comments,
                'bad_comments': bad_comments,
                'good_rate': rate_comments
            }
        elif len(ul)==7 :# 對應第二類商品
            comments_button = wait.until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '#detail > div.tab-main.large > ul > li:nth-child(5)')))
            comments_button.click()#點擊「評價」按鈕
            rate = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#comment > div.mc > div.comment-info.J-comment-info > div.comment-percent > div')))
            rate_comments = rate.text# rate是webElement類型,調用text便可獲取對應的文本,注意不是調用text()

            # 等待差評數量加載完成(差評數量加載完了也就意味着好評和中評數量加載完了)
            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#comment > div.mc > div.J-comments-list.comments-list.ETab > div.tab-main.small > ul > li:nth-child(7) > a > em')))
            tmp_doc = pq(browser.page_source,parser='html')
            comments_obtain = tmp_doc('.tab-main .filter-list li' ).items()
            index = 0
            for comment in comments_obtain:
                if index==4:
                    good_comments = comment('a em').text()[1:-1]
                elif index==5:
                    neutral_comments = comment('a em').text()[1:-1]
                elif index==6:
                    bad_comments = comment('a em').text()[1:-1]
                    break
                index = index + 1
            yield {
                'name': good.find('.p-name a em').text().strip().replace('\n', ' '),
                'price': good.find('.p-price i').text(),
                'total_comments': good.find('.p-commit a').text(),
                'good_comments':good_comments,
                'neutral_comments':neutral_comments,
                'bad_comments':bad_comments,
                'good_rate': rate_comments
            }
    browser.switch_to.window(browser.window_handles[0])
複製代碼

資料分享

java學習筆記、10T資料、100多個java項目分享瀏覽器


歡迎關注我的公衆號【菜鳥名企夢】,公衆號專一:互聯網求職面經javapython爬蟲大數據等技術分享**: 公衆號**菜鳥名企夢後臺發送「csdn」便可免費領取【csdn】和【百度文庫】下載服務; 公衆號菜鳥名企夢後臺發送「資料」:便可領取5T精品學習資料**、java面試考點java面經總結,以及幾十個java、大數據項目資料很全,你想找的幾乎都有 框架

掃碼關注,及時獲取更多精彩內容。(博主今日頭條大數據工程師)
相關文章
相關標籤/搜索