Twitter 自動上傳圖文
這篇文章會使用 Python 的 Selenium 函式庫,實作一個可以自動登入 Twitter,自動上傳圖片以及發佈 Twitter 的爬蟲,同時也會讓 selenium 所做出的爬蟲的程式在背景運作,不會影響到其他視窗的工作。
快速導覽:
執行 selenium 會啟動 chromedriver,所以所以請使用本機環境 ( 參考:使用 Python 虛擬環境 ) 或使用 Anaconda Jupyter 進行實作 ( 參考:使用 Anaconda ) 。
設定 selenium 爬蟲環境
由於 Twitter 本身也有做「基本」的反爬蟲 ( 辨識瀏覽器 Headers 資訊 ),所以需要提供偽裝後的資訊。
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15"
opt = webdriver.ChromeOptions()
driver = webdriver.Chrome('./chromedriver', options=opt)
# 清空 window.navigator
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
點擊登入按鈕
用 Chrome 開啟 Twitter 的首頁。
Twitter:https://twitter.com/
將滑鼠移動至右方的「登入」的位置,按下右鍵,選擇「檢查」,可以看見登入按鈕是一個 div 和兩個 span 放在一個 a 的 tag 裡的結構,a 的 tag 具有 href 為「/login」的屬性。
接續上方的程式,撰寫下方的程式碼,執行後會先將視窗往下捲動 ( 登入按鈕露出 ),接著自動點擊登入按鈕。
driver.get('https://twitter.com')
sleep(2)
driver.execute_script(f'window.scrollTo(0, 200)') # 自動往下捲動 200px
login = driver.find_element(By.CSS_SELECTOR, 'a[href="/login"]') # 取得登入按鈕
login.click() # 點擊登入按鈕
輸入 Email 帳號
登入後,畫面中會出現輸入登入資訊的視窗,點擊輸入 email 的欄位,按下右鍵,選擇「檢查」,找到「輸入文字」的 input tag,以及「下一步」的按鈕 div tag,記下 input 的 autocomplete 屬性和 div 的 role 屬性。
注意,如果發現 tag 裡有很多無意義英文數字組成的 class、id 或屬性,往往是系統亂數產生,如果使用爬蟲爬取這些自動產生的名稱,可能會發生「找不到」的錯誤,因此要尋找有意義的名稱。
接續上方的程式繼續撰寫,先抓取並自動輸入 email,然後再抓取 div 元素,由於屬性 role 為 button 的 div 有好幾個,所以使用內容判斷,如果 div 的內容為「下一步」或「Next」表示可以點擊。
由於爬蟲在開啟瀏覽器的狀態下,Twitter 介面會自動變成中文,背景執行時會變成是英文,所以用 or 做判斷。
sleep(2) # 等待兩秒,讓網頁載入完成
# 取得輸入 email 的輸入框
username = driver.find_element(By.CSS_SELECTOR, 'input[autocomplete="username"]')
username.send_keys('你的 email') # 輸入 email
print('輸入 email 完成')
# 取得畫面上所有按鈕 ( 使用 elements )
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '下一步' or i.text == 'Next':
i.click() # 如果按鈕是「下一步」或「Next」就點擊
print('點擊下一步')
break
避開安全性頁面
由於是使用爬蟲登入,所以 Twitter 基於安全性考量,「有可能」會額外彈出一個視窗,要求輸入 Twitter 帳號做進一步確認,仿照上面的做法,找到對應元素 HTML 代碼。
接續上方的程式繼續撰寫,使用 try 和 except 的做法,在沒有安全性頁面時,直接等待兩秒就繼續,如果有安全性頁面時,自動輸入帳號並進行下一步。
sleep(2) # 等待兩秒頁面載入後繼續
try:
check = driver.find_element(By.CSS_SELECTOR, 'input[autocomplete="on"]')
check.send_keys('你的帳號') # 輸入帳號
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '下一步' or i.text == 'Next':
i.click() # 如果按鈕是「下一步」或「Next」就點擊
print('驗證使用者帳號,點擊下一步')
break
sleep(2) # 等待兩秒頁面載入後繼續
except:
print('ok')
sleep(2) # 如果沒有出現安全性畫面,等待兩秒頁面載入後繼續
輸入密碼,登入 Twitter
輸入 email 或通過安全性檢查頁面後,會出現密碼輸入的畫面,仿照上面的做法,找到對應元素 HTML 代碼。
接續上方的程式繼續撰寫,抓取密碼輸入的 input 欄位,填入密碼後抓取並點擊登入的按鈕。
pwd = driver.find_element(By.CSS_SELECTOR, 'input[autocomplete="current-password"]')
pwd.send_keys('你的密碼')
print('輸入密碼')
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '登入' or i.text == 'Log in':
i.click()
print('點擊登入')
break
上傳圖片並發佈 Twitter
登入後,仿照上面的做法,找到輸入文字、上傳圖片的按鈕和推文按鈕的網頁元素 HTML。
輸入文字的欄位,是使用 div 和 span 組成。
上傳圖片的按鈕雖然是 div,但本質上仍然是使用 type 為 file 的 input 元素。
發佈 Twitter 的按鈕跟之前一樣都是 role 為 button 的 div。
接續上方的程式繼續撰寫,先在輸入文字的欄位填上文字,接著直接針對 input 欄位提供圖片在電腦中的「絕對路徑」,完成後點擊推文按鈕,程式執行後就會自動上傳圖片,並發佈 Twitter。
自動發佈的 Twitter:連結
sleep(2)
textbox = driver.find_element(By.CSS_SELECTOR, 'div[role="textbox"]')
textbox.send_keys('Hello World!I am Robot~ ^_^') # 在輸入框輸入文字
print('輸入文字')
sleep(1)
imgInput = driver.find_element(By.CSS_SELECTOR, 'input[data-testid="fileInput"]')
imgInput.send_keys('/Users/oxxo/Desktop/oxxo.png') # 提供圖片絕對路徑,上傳圖片
print('上傳圖片')
sleep(1)
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '推文' or i.text == 'Tweet':
i.click() # 點擊推文按鈕
print('推文完成')
break
sleep(1)
driver.close() # 關閉瀏覽器視窗
讓爬蟲在背景執行
程式全部完成後,可以額外設定「--headless」,讓爬蟲程式在「背景執行」,避免開啟瀏覽器視窗時,影響其他視窗的工作 ( 如果將爬蟲的視窗縮小,則爬蟲會發生錯誤,無法和其他視窗同時工作 )
小結
透過 Twitter 爬蟲的自動上傳圖文,可以練習到使用 Selenium 函式庫操作「背景執行」以及「上傳圖片」,如果再搭配一些時間的排程,就能做到全自動定時發 Twitter 的效果囉!
完成程式碼
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15"
opt = webdriver.ChromeOptions()
opt.add_argument('--headless')
opt.add_argument('--user-agent=%s' % user_agent)
driver = webdriver.Chrome('./chromedriver', options=opt)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
driver.get('https://twitter.com')
sleep(2)
driver.execute_script(f'window.scrollTo(0, 200)')
login = driver.find_element(By.CSS_SELECTOR, 'a[href="/login"]')
login.click()
sleep(2)
username = driver.find_element(By.CSS_SELECTOR, 'input[autocomplete="username"]')
username.send_keys('你的 email')
print('輸入 email 完成')
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '下一步' or i.text == 'Next':
i.click()
print('點擊下一步')
break
sleep(2)
try:
check = driver.find_element(By.CSS_SELECTOR, 'input[autocomplete="on"]')
check.send_keys('你的帳號')
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '下一步' or i.text == 'Next':
i.click()
print('驗證使用者帳號,點擊下一步')
break
sleep(2)
except:
print('ok')
sleep(2)
pwd = driver.find_element(By.CSS_SELECTOR, 'input[autocomplete="current-password"]')
pwd.send_keys('你的密碼')
print('輸入密碼')
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '登入' or i.text == 'Log in':
i.click()
print('點擊登入')
break
sleep(2)
textbox = driver.find_element(By.CSS_SELECTOR, 'div[role="textbox"]')
textbox.send_keys('Hello World!I am Robot~ ^_^')
print('輸入文字')
sleep(1)
imgInput = driver.find_element(By.CSS_SELECTOR, 'input[data-testid="fileInput"]')
imgInput.send_keys('/Users/oxxo/Desktop/oxxo.png')
print('上傳圖片')
sleep(1)
buttons = driver.find_elements(By.CSS_SELECTOR, 'div[role="button"]')
for i in buttons:
if i.text == '推文' or i.text == 'Tweet':
i.click()
print('推文完成')
break
sleep(1)
driver.close()
意見回饋
如果有任何建議或問題,可傳送「意見表單」給我,謝謝~