透過Selenium做Instagram自動發文機器人

內容目錄

簡介

Instagram是現今最受歡迎的社交媒體之一,每天有數以萬計的用戶在上面分享照片和影片。但是,對於那些管理多個帳戶或需要定期發文的人來說,手動發文可能會非常繁瑣和耗時。

這是在Python中自動化發文的好處所在。在這篇文章中,我們將討論如何使用Python腳本自動化在Instagram上發文。

第零步 確認環境

這一步其實滿重要的,尤其是最近Selenium大更新,把找尋網頁元素的function改寫了,所以很多舊的教學文就不太適用了。

這篇教學文會用到的環境版本如下:
python >= 3.7.0
selenium >= 4.4.3
而方才提到主要更改的是find_element這個關鍵的function。

第一步 載入會用到的函式庫以及定義function

這一步其實滿重要的,因為接下來上傳檔案會使用「拖拉」的方式,所以必須要載入這個別人寫好的function。

import glob, time, os
from selenium.webdriver.remote.webelement import WebElement
import os.path
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.microsoft import EdgeChromiumDriverManager

# 定義好會用到的函式
# 這邊參考了網路上有人提供的拖拉式放檔案的方法,可以用在所有「拖曳你的檔案至此」區塊
# JavaScript: HTML5 File drop
# source            : https://gist.github.com/florentbr/0eff8b785e85e93ecc3ce500169bd676
# param1 WebElement : Drop area element
# param2 Double     : Optional - Drop offset x relative to the top/left corner of the drop area. Center if 0.
# param3 Double     : Optional - Drop offset y relative to the top/left corner of the drop area. Center if 0.
# return WebElement : File input

JS_DROP_FILES = "var k=arguments,d=k[0],g=k[1],c=k[2],m=d.ownerDocument||document;for(var e=0;;){var f=d.getBoundingClientRect(),b=f.left+(g||(f.width/2)),a=f.top+(c||(f.height/2)),h=m.elementFromPoint(b,a);if(h&&d.contains(h)){break}if(++e>1){var j=new Error('Element not interactable');j.code=15;throw j}d.scrollIntoView({behavior:'instant',block:'center',inline:'center'})}var l=m.createElement('INPUT');l.setAttribute('type','file');l.setAttribute('multiple','');l.setAttribute('style','position:fixed;z-index:2147483647;left:0;top:0;');l.onchange=function(q){l.parentElement.removeChild(l);q.stopPropagation();var r={constructor:DataTransfer,effectAllowed:'all',dropEffect:'none',types:['Files'],files:l.files,setData:function u(){},getData:function o(){},clearData:function s(){},setDragImage:function i(){}};if(window.DataTransferItemList){r.items=Object.setPrototypeOf(Array.prototype.map.call(l.files,function(x){return{constructor:DataTransferItem,kind:'file',type:x.type,getAsFile:function v(){return x},getAsString:function y(A){var z=new FileReader();z.onload=function(B){A(B.target.result)};z.readAsText(x)},webkitGetAsEntry:function w(){return{constructor:FileSystemFileEntry,name:x.name,fullPath:'/'+x.name,isFile:true,isDirectory:false,file:function z(A){A(x)}}}}}),{constructor:DataTransferItemList,add:function t(){},clear:function p(){},remove:function n(){}})}['dragenter','dragover','drop'].forEach(function(v){var w=m.createEvent('DragEvent');w.initMouseEvent(v,true,true,m.defaultView,0,0,0,b,a,false,false,false,false,0,null);Object.setPrototypeOf(w,null);w.dataTransfer=r;Object.setPrototypeOf(w,DragEvent.prototype);h.dispatchEvent(w)})};m.documentElement.appendChild(l);l.getBoundingClientRect();return l"

def drop_files(element, files, offsetX=0, offsetY=0):
    driver = element.parent
    isLocal = not driver._is_remote or '127.0.0.1' in driver.command_executor._url
    paths = []

    # ensure files are present, and upload to the remote server if session is remote
    for file in (files if isinstance(files, list) else [files]) :
        if not os.path.isfile(file) :
            raise FileNotFoundError(file)
        paths.append(file if isLocal else element._upload(file))

    value = '\n'.join(paths)
    elm_input = driver.execute_script(JS_DROP_FILES, element, offsetX, offsetY)
    elm_input._execute('sendKeysToElement', {'value': [value], 'text': value})

WebElement.drop_files = drop_files

第二步 打開瀏覽器並登入帳號

這邊使用的是Edge瀏覽器,想要換成其他的就自行替換掉即可。

#開啟瀏覽器
driver = webdriver.Edge(EdgeChromiumDriverManager().install())
driver.get('https://www.instagram.com/accounts/login/')
time.sleep(2)

# 設定帳號密碼
login_id = "帳號"
login_pd = "密碼"

#登入
driver.find_element(by=By.NAME, value = "username").send_keys(login_id)
driver.find_element(by=By.NAME, value = "password").send_keys(login_pd)
driver.find_element(By.XPATH, '//button[normalize-space()="登入"]').click()
time.sleep(2)

#進入主畫面
driver.get(f"https://www.instagram.com/{login_id}/")

重點一、透過XPath找尋畫面中的「文字」

由於Instagram的架構問題(不知道是不是刻意的)它的Xpath路徑常常會改變,甚至是沒有固定的id或是其他比較容易定位的元素。

因此這邊採用find_element(By.XPATH, '//button[normalize-space()="文字"]')這樣的方法來找尋畫面中呈現特定「文字」的元素

重點二、f-string

在python中要把變數插入到字串當中有很多用法,以前喜歡寫成這樣:
"Hello, my name is {}".format(my_name)
但我長大了,我發現f-string比較好用,請改寫成:
f"Hello, my name is {my_name}"

步驟三 透過「拖拉」的方式上傳單張照片

#點擊上傳按鈕
new_post_button = driver.find_element(By.CSS_SELECTOR,'[aria-label="新貼文"]')
new_post_button.click()
time.sleep(2)
dropzone = driver.find_element(By.CSS_SELECTOR, '[aria-label="表示圖像或影片等影音素材的圖示"]')
# 選擇一個圖片、影片
image_path = r"image_1.png"     #注意把路徑前面加上r,這樣才不會因為路徑中特定的字元讓程式不認得
dropzone.drop_files(image_path)
time.sleep(2)
# 縮放 (這邊選擇原始比例,如果要1:1那可以直接跳過這段)
driver.find_element(By.CSS_SELECTOR, '[aria-label="選擇「裁切」"]').click()
time.sleep(2)
# 挑選你要的比例,其他的請註解掉(前面加上#的意思)
driver.find_element(By.XPATH, '//button[normalize-space()="原始"]').click()
driver.find_element(By.XPATH, '//button[normalize-space()="1:1"]').click()
driver.find_element(By.XPATH, '//button[normalize-space()="4:5"]').click()
driver.find_element(By.XPATH, '//button[normalize-space()="16:9"]').click()
# 下一步
time.sleep(2)
driver.find_element(By.XPATH, '//button[normalize-space()="下一步"]').click()
# 濾鏡選擇,這邊不選擇,直接下一步
time.sleep(2)
driver.find_element(By.XPATH, '//button[normalize-space()="下一步"]').click()

重點三、透過「aria-label」來定位

前面有提到,透過Xpath的位置來定位在Instagram的頁面上不太實際,所以我們要各種利用其他的方式來定位我們要互動、操作的元素。

在這個步驟中,我們透過By.CSS_SELECTOR這個方法搭配aria-label來去定位那些沒有特定文字的元素。

重點四、路徑請愛用 r-string

只要是碰到有路徑的,我一律用r-string來解決,因為檔名的可能性太多,搭配上前面的斜線很容易就變成特定的字符,所以只要遇到路徑,就請在冒號前面加上一個r。

步驟四 設定內文、Tag並送出文章

# 內文設計
message = """
自動發文好快樂,嘿嘿
#IG_Robot #Mortis #lalala
#Python
"""
# 填寫內文
post = driver.find_element(By.CSS_SELECTOR, '[aria-label="撰寫說明文字……"]')
post.send_keys(message)
time.sleep(2)

# 送出
driver.find_element(By.XPATH, '//button[normalize-space()="分享"]').click()

# 關閉浮動視窗
driver.find_element(By.CSS_SELECTOR, '[aria-label="關閉"]').click()
time.sleep(20)

以上,就是一個簡單透過python搭配selenium在instagram上發文的教學文。

重點五、網站結構是會改變的

任何網站結構都是會改變的,所以太舊的教學文很有可能無法直接照抄、照搬來用,這也是所有爬蟲或是自動化的痛點。

也因為意識到網站Layout改變的頻率變高,我也盡量少用Xpath來做定位,開始偏向已接近人類找尋文字、按鈕的方法來寫自動化腳本。

發佈留言