【Day 20】- 让 Selenium 去 Dcard 上自动向下卷动 (实战 Selenium 模拟使用者划手机 2/2)

前情提要

前一篇实作了 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/


<<:  大共享时代系列_019_水电、装潢、建筑工程交流与媒合

>>:  你是谁、你的过去都不重要,成功的能力永远都从你开始。

p段落标签-基础用法

p段落标签最常使用搭配段落文章使用 同时也是一个display:block特性的元素 <p&g...

ASP.NET MVC 从入门到放弃 (Day7) -C#物件导向介绍(封装 继承 多型

接着来讲讲常用的物件导向一些基本概念.... 封装 可能你知道套件函式名称,但不知道里面是什麽就叫封...

Day11 - 在 Next.js 中使用 CSR - feat. useSWR

为什麽我们需要 SWR ? 先前我们已经了解了 CSR、SSR 与 SSG 的优劣,SSR 与 SS...

[day16]机器人对话纪录

以前遇到一个情况阿,使用者输入,我要ㄧ个汉堡,二杯奶茶,到後台却变成,我要\xe3\x84\xa7个...

[Day15] Vite 出小蜜蜂~随机射击 Randomly Shoot!

Day15 在 Space Invaders 的游戏设计中, 除了随着不断前进而产生的压迫感之外, ...