iT邦幫忙

2021 iThome 鐵人賽

DAY 20
1
AI & Data

網路爬蟲,萬物皆爬 - 30 天搞懂並實戰網路爬蟲及應對反爬蟲技術系列 第 20

【Day 20】- 讓 Selenium 去 Dcard 上自動向下捲動 (實戰 Selenium 模擬使用者划手機 2/2)

  • 分享至 

  • xImage
  •  

前情提要

前一篇實作了 Selenium 爬取 Dcard 文章的爬蟲,可以看到會出現一個瀏覽器視窗模擬使用者,並使用內建的選擇器鎖定文章資訊。

開始之前

首先,Dcard 的設計是不會一次將所有文章皆讀取完成,而是每次使用者捲動到底部時讀取新的文章,因此如果要寫爬取 Dcard 的爬蟲(不使用 Dcard API)需要模擬使用者捲動螢幕。

今天要帶各位加上的功能是模擬使用者滾動螢幕,當爬取完成目前的所有內容後,自動滾動螢幕到最底部,之後再進行爬取,以此達到取得 Dcard 上文章的目的。

預期效果

  1. 使用者可以輸入想要捲動幾次(爬取多少文章)
  2. 爬取當前文章(需紀錄上一次的最後一篇文章,並不紀錄那篇文章之前的所有文章,以確保不會重複爬取)
  3. 每次爬取完成都會捲動螢幕到最底部。
  4. 重複 (2) 直到爬取了使用者輸入的次數

實作

首先,我們先實作使用者輸入捲動次數的程式,並完成初步 Selenium 程式。

from selenium import webdriver
from time import sleep
import json

if __name__ == '__main__':
    scroll_time = int(input('請輸入想要捲動幾次'))
    driver = webdriver.Chrome()
    driver.get('https://www.dcard.tw/f')

接下來,我們要完成程式捲動的部分。

在 Selenium 中支援執行 JavaScript 的功能,因此我們能透過 JavaScript 的 window.scrollTo 來達到捲動螢幕的效果。

from selenium import webdriver
from time import sleep
import json

if __name__ == '__main__':
    scroll_time = int(input('請輸入想要捲動幾次'))
    driver = webdriver.Chrome()
    driver.get('https://www.dcard.tw/f')
    sleep(2)
    js = "window.scrollTo(0, document.body.scrollHeight);"
    driver.execute_script(js)

完成捲動螢幕後,我們寫一個 for-loop 執行使用者輸入的次數。

from selenium import webdriver
from time import sleep
import json

if __name__ == '__main__':
    scroll_time = int(input('請輸入想要捲動幾次'))
    driver = webdriver.Chrome()
    driver.get('https://www.dcard.tw/f')
    for now_time in range(1, scroll_time+1):
        sleep(2)
        print(f"now scroll {now_time}/{scroll_time}")
        js = "window.scrollTo(0, document.body.scrollHeight);"
        driver.execute_script(js)

接下來,我們能將昨天實作的文章爬取寫入到迴圈當中。

有時會遇到無法找尋到元素的問題(文章刪除或廣告),因此我們能在爬取文章資訊的迴圈中加個 try-except,來讓程式不會因此停止。

from selenium import webdriver
from time import sleep
import json

if __name__ == '__main__':
    scroll_time = int(input('請輸入想要捲動幾次'))
    driver = webdriver.Chrome()
    driver.get('https://www.dcard.tw/f')
    results = []
    for now_time in range(1, scroll_time+1):
        sleep(2)
        eles = driver.find_elements_by_class_name('tgn9uw-0')
        for ele in eles:
            try:
                title = ele.find_element_by_class_name('tgn9uw-3').text
                href = ele.find_element_by_class_name(
                    'tgn9uw-3').get_attribute('href')
                subtitle = ele.find_element_by_class_name('tgn9uw-4').text
                result = {
                    'title': title,
                    'href': href,
                    'subtitle': subtitle
                }
                results.append(result)
            except:
                pass
        print(f"now scroll {now_time}/{scroll_time}")
        js = "window.scrollTo(0, document.body.scrollHeight);"
        driver.execute_script(js)
    with open('Dcard-articles.json', 'w', encoding='utf-8') as f:
        json.dump(results, f, indent=2,
                  sort_keys=True, ensure_ascii=False)
    driver.quit()

這樣會有一個明顯的問題,會導致有些文章會重複爬取,如下圖。

解決這個問題也非常容易,只要記錄上一次爬取的最後一個文章元素,之後下次爬取時將那個元素之前的元素刪除即可(或者只進行爬取那個元素之後的元素)

from selenium import webdriver
from time import sleep
import json

if __name__ == '__main__':
    scroll_time = int(input('請輸入想要捲動幾次'))
    driver = webdriver.Chrome()
    driver.get('https://www.dcard.tw/f')
    results = []
    prev_ele = None
    for now_time in range(1, scroll_time+1):
        sleep(2)
        eles = driver.find_elements_by_class_name('tgn9uw-0')
        # 若串列中存在上一次的最後一個元素,則擷取上一次的最後一個元素到當前最後一個元素進行爬取
        try:
            # print(eles)
            # print(prev_ele)
            eles = eles[eles.index(prev_ele):]
        except:
            pass
        for ele in eles:
            try:
                title = ele.find_element_by_class_name('tgn9uw-3').text
                href = ele.find_element_by_class_name(
                    'tgn9uw-3').get_attribute('href')
                subtitle = ele.find_element_by_class_name('tgn9uw-4').text
                result = {
                    'title': title,
                    'href': href,
                    'subtitle': subtitle
                }
                results.append(result)
            except:
                pass
        prev_ele = eles[-1]
        print(f"now scroll {now_time}/{scroll_time}")
        js = "window.scrollTo(0, document.body.scrollHeight);"
        driver.execute_script(js)
    with open('Dcard-articles.json', 'w', encoding='utf-8') as f:
        json.dump(results, f, indent=2,
                  sort_keys=True, ensure_ascii=False)
    driver.quit()

到目前為止,功能已完成。

結語

今天達成了在 Selenium 中執行 JavaScript 語句達成捲動螢幕的效果,並透過擷取串列達到不重複爬取的效果。

明日內容

明天會帶各位實戰 Instagram 上自動點擊好友貼文讚的爬蟲。

補充資料

Dcard : https://www.dcard.tw/f

Selenium with Python docs : https://selenium-python.readthedocs.io/

Selenium docs : https://readthedocs.org/projects/selenium-python/downloads/pdf/latest/


上一篇
【Day 19】- 讓 Selenium 去 Dcard 上爬文! (實戰 Selenium 模擬使用者划手機 1/2)
下一篇
【Day 21】- 你的爬蟲還在用帳號密碼進行登入? 帶上 Session 吧!(實戰 Selenium 自動點擊 Instagram 好友貼文贊 1/2)
系列文
網路爬蟲,萬物皆爬 - 30 天搞懂並實戰網路爬蟲及應對反爬蟲技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言