大家好,我是一宵三筵
鐵人賽已經開跑了,我這邊今年是第一次參加鐵人賽
那鐵人賽就是會存一些草稿嘛,每天再把文章發出去就行
但可能就會遇到一個問題...
「如果我忘記發文怎麼辦?」
或是我看到有些問答,也會討論說可能出國去了,或是有事真的需要設排程
在此希望iT邦幫忙可以出排程發文的功能... 如果有我就不用那麼辛苦了
總之我沉思幾秒鐘,就決定來做自動將草稿發文的程式了
我這邊要先聲明,我不會寫Python
平常最常接觸的語言是JS/TS、React、nodeJS
jQuery和php也會,但就是不會python
所以以下的程式,是一邊問ChatGPT一邊寫出來的
之後是怎麼從0到有的研究過程,會放在我的鐵人賽系列文中!
這邊推廣一下我的鐵人賽文章: 用ChatGPT詠唱來完成工作與點亮前後端技能樹
我真的用詠唱學python,從安裝到裡面各種語法 (哭泣)
最後花了兩天時間寫完~
OK教學正文開始:
首先,我已經將程式放上github了: github連結
最核心有關發布、取得列表資料的部分,這邊也可以進行複製utils.py
import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import urlencode
from configparser import ConfigParser
config = ConfigParser()
config.read('itHelpConfig.ini')
lineToken = config.get('Settings', 'line_token')
# 建立session
def getSession(cookie_value = ''):
    session = requests.Session()
    # 設定 User-Agent 標頭
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }
    # 將 Cookie 存儲在 Session 中
    session.headers.update(headers)
    session.cookies['Cookie'] = cookie_value
    return session
# 發出request取得網站內容或回應
def scrape_website(session, url, method = 'GET', data = {}):
    if method.upper() == 'GET':
        response = session.get(url)
    else :
        response = session.post(url, data=data, json=data)
    # 檢查請求的回應
    if response.status_code == 200:
        # 處理回應內容
        return (response.text)
    else:
        raise Exception(f'請求失敗,狀態碼:{response.status_code}, 錯誤訊息:{response.text}')
#發送到line通知    
def line_notify(message):
    try:
        url = 'https://notify-api.line.me/api/notify'
        data = { 'message': message }
        lineSession = getSession('')
        lineSession.headers = {"Authorization": f"Bearer {lineToken}"}
        lineSession.headers.update(lineSession.headers)
        scrape_website(lineSession, url, 'POST', data)
    except Exception as error:
        print(f'串接line失敗 : {error}')
# 取得使用者{id: string, name: string} | None 
def getUser(session):
    url = 'https://ithelp.ithome.com.tw/'
    page_content = scrape_website(session, url)
    soup = BeautifulSoup(page_content, 'html.parser')
    account = soup.find('a', {'id': 'account'})
    if account :
        name = account['data-account']
        href = account['href']
        id = re.search(r'users/(\d+)', href).group(1)
        return { 'name': name , 'id': id }
    else:
        return None
def editDraft(session, articlesId, Content):
    draftContent = getDraftContent(session, articlesId)
    url = f'https://ithelp.ithome.com.tw/articles/{articlesId}/draft'
    token = draftContent['token']
    newHeaders = session.headers
    newHeaders['X-Csrf-Token'] = token
    newHeaders['Origin'] = 'https://ithelp.ithome.com.tw'
    session.headers.update(newHeaders)
    subject = Content['subject']
    data = {'_token': token,
         'group': 'tech',
         '_method': 'PUT',
         'subject': subject,
         'description': Content['description'],
         'tags[]': Content['tags'],
        }
    # 將tags[]的值編碼
    encoded_data = urlencode(data, doseq=True)
    scrape_website(session, url, 'POST', encoded_data)
# 取得草稿列表 Array<{link:string, text: string, id:string}>
def getArticlesList(session):
    user = getUser(session)
    if user == None:
        raise Exception('登入狀態過期,無法取得文章列表')
    userId = user['id']
    url = f'https://ithelp.ithome.com.tw/users/{userId}/articles'
    page_content = scrape_website(session, url)
    soup = BeautifulSoup(page_content, 'html.parser')
    div_elements = soup.find_all('div', class_='qa-list profile-list')
    if div_elements:
        # 遍歷所有找到的元素,並處理它們
        # 使用列表推導式篩選出包含 <span class="title-badge title-badge--draft"> 的 <div> 元素
        filtered_div_elements = [div_element for div_element in div_elements if div_element.find('span', class_='title-badge title-badge--draft')]
        # 檢查篩選後的結果
        results = []  # 創建一個空的物件陣列來存儲結果
        if filtered_div_elements:
            for  list_element in filtered_div_elements:
                title_link = list_element.find('a', class_='qa-list__title-link')
                link = title_link.get('href').strip()
                text = title_link.text.strip()
                id = re.search(r'articles/(\d+)', link).group(1)
                result_dict = {'link': link, 'text': text, 'id': id}
                results.append(result_dict)
        results.reverse()
        return results
    else:
        return []
# 取得指定草稿文章的資料內容
def getDraftContent(session, articlesId):
    url = f'https://ithelp.ithome.com.tw/articles/{articlesId}/draft'
    page_content = scrape_website(session, url)
    soup = BeautifulSoup(page_content, 'html.parser')
    input_element = soup.find('input', {'name': '_token'})
    if input_element == None:
        raise Exception(f'發文token取得失敗')
    token = input_element['value']
    subject = soup.find('input', {'name': 'subject'})['value']
    description =  soup.find('textarea', {'name': 'description'}).text
    tag_elements = soup.find('select', {'name': 'tags[]'}).find_all('option', selected='selected')
    tags = [option.text for option in tag_elements]
    return { 
            'token': token,
            'description': description,
            'subject': subject,
            'tags': tags
        }
def publish(session, articlesId):
    draftContent = getDraftContent(session, articlesId)
    url = f'https://ithelp.ithome.com.tw/articles/{articlesId}/publish'
    newHeaders = session.headers
    newHeaders['X-Csrf-Token'] = draftContent['token']
    newHeaders['Origin'] = 'https://ithelp.ithome.com.tw'
    session.headers.update(newHeaders)
    subject = draftContent['subject']
    data = {'_token': draftContent['token'],
         'group': 'tech',
         '_method': 'PUT',
         'subject': subject,
         'description': draftContent['description'],
         'tags[]': draftContent['tags'],
        }
    # 將tags[]的值編碼
    encoded_data = urlencode(data, doseq=True)
    scrape_website(session, url, 'POST', encoded_data)
    message = f'已發布文章! 文章名稱: {subject} ; id: {articlesId}'
    line_notify(message)
    print(message)
def login(loginId, ps):
    # 去登入看看
    session = getSession()
    url = 'https://member.ithome.com.tw/login'
    loginPage = scrape_website(session, url)
    soup = BeautifulSoup(loginPage, 'html.parser')
    token = soup.find('input', {'name': '_token'})['value']
    newHeaders = session.headers
    newHeaders['X-Csrf-Token'] = token
    newHeaders['Origin'] = 'https://member.ithome.com.tw'
    # 將 Cookie 存儲在 Session 中
    session.headers.update(newHeaders)
    data = {'_token': token, 'account': loginId, 'password': ps}
    response = session.post(url, data=data)
     # 檢查請求的回應
    if response.status_code == 200:
        # 處理回應內容
        if (response.cookies):
            cookie_dict = requests.utils.dict_from_cookiejar(response.cookies)
            cookie_string = '; '.join([f'{key}={value}' for key, value in cookie_dict.items()])
            return cookie_string
    else:
        raise Exception(f'請求失敗,狀態碼:{response.status_code}, 錯誤訊息:{response.text}')
# 作者: 一宵三筵 (lalame888)
IT邦幫忙鐵人賽發文神器
只要貼上登入token,就可以檢視目前草稿的標題與數量
並且可開關自動發文功能,每天於設定時間(預設為10:00:00,可以更動)自動貼一篇貼文
避免失去挑戰資格
若有串接line notify
於發文後也會傳送line通知,或是程式崩潰時也會傳送通知
設定步驟:
登入IT邦幫忙主頁 : https://ithelp.ithome.com.tw/
確定已經登入之後,按下F12,然後再重整一次
這時候點到F12中的網路,找到最上方的request
查看請求(request)的標頭Header中的 Cookie

就是圖片中反白的那一大塊
複製之後,把他貼到itHelpConfig.ini中

(圖片中露出來的cookie是不同的登入帳號,所以內容長不一樣正常的,單純示範)
如果希望程式起動與發佈貼文之後可以透過line傳送訊息給自己
先進入到 https://notify-bot.line.me/zh_TW/
然後登入之後,右上角名字選單點開,點擊「個人頁面」

接著頁面來到最下方,點擊發行權杖
接著輸入服務名字(看得懂是IT邦幫忙用的就好)
下方可以選擇1v1的聊天,或是可以自己創一個群組,然後把notify機器人邀進那個群組
好了之後就會得到一組token,把token複製起來
(這個示範用的token我創完就刪掉了,所以露出來沒關係)
把複製好的token,貼到itHelpConfig.ini中的line_token= 後方
之後的通知效果會像是:
python,確認是否成功安裝。python,確認是否成功安裝。在大多數情況下,安裝 Python 的同時也會自動安裝 pip。不過,如果你確定 pip 沒有安裝或者需要更新,你可以按照以下步驟來進行安裝:
get-pip.py 腳本。可以從 https://bootstrap.pypa.io/get-pip.py 連結進行下載。你可以在瀏覽器中開啟這個連結,然後將網頁另存為 get-pip.py 檔案。get-pip.py 檔案的目錄。python3 get-pip.py
可以在終端機或命令提示字元中執行以下指令來檢查 pip 是否已成功安裝:
pip --version
如果成功安裝,將會顯示 pip 的版本號。
以下是這個專案會用到的套件:
pip install requests
pip install schedule
pip install beautifulsoup4
開啟終端機或是用VS Code等開啟專案資料夾
接著於終端機中輸入
python3 itHelp.py

如果有串接line的話就會同步收到通知
看起來卡在選單中,但背景還是有在運作,就把終端視窗開著
這是最重要的一步,必須 「照順序」 將你要發布的草稿給儲存起來

用程式讀起來就會變成這樣
會從最早的草稿開始,當時間到就自動發文
pyinstaller --onefile itHelp.py 進行打包專案成單獨的執行檔卻一直失敗,將ini檔放在執行檔旁邊也是上面的程式說真的沒有寫得很好,我也沒有多去做整理,用兩天簡單寫完的
裡面有亂七八糟的程式碼跟全域變數跑來跑去 (對不起裡面在開副作用派對)
程式碼中,有很多片段都是我瘋狂問ChatGPT所得到的
「python陣列長度要怎麼取得」
「怎麼讓使用者輸入內容」
「eles if 在python中的語法」
「js的 `標題:${value}` 在python中要怎麼寫」
「怎麼發request、怎麼設定cookie」
「怎麼讀config」
中間狂發測試文章,那個多tag的格式真的很難搞,之後放系列文裡面
也希望小財神這兩天沒有被我的各種半夜405、419、403、500 request吵到爬起來debug...
沒有bug只是有個小王八蛋在試圖串你家的api...
it邦幫忙前端也是jQuery寫的呢,嗯。
最後,目前的專案,還有很多可以改良的地方
像是串line bot、串登入、打包成更方便直覺使用的exe等
如果有相關的教學或是要直接對程式提出改良,歡迎提出!
我是一宵三筵,再關注我的系列文章: 用ChatGPT詠唱來完成工作與點亮前後端技能樹
然後本篇內容的實作過程放在這裡
【Day54】ChatGPT幫我完成工作:不會python也能用python爬蟲做出IT邦幫忙自動發文神器
明天見~
LINE Notify SDK 參考 https://github.com/louis70109/lotify
謝謝提供~不過我看範例都是由程式端去發訊息給notify,然後line上只能傳訊息不能做互動
串notify的部分、單純傳訊息、圖片是沒問題
我想做的是可以透過line bot去遠端調整程式的設定~
例如檢視還沒發佈的草稿列表、或是修改自動發文的時間之類的
我看了你的系列文!
感覺比較是透過webhook去接受bot的訊息,以及用bot推播?
https://ithelp.ithome.com.tw/articles/10221451
這篇?
對,如果你走的入口是從 LINE Bot 的話,那就是走 webhook 去收來自 LINE 的 event,也可以參考均民大大的作法唷
如果是用python + LINE Bot 的話,可以參考看看我的 repo,主要就是用 flex message 去打 API 觸發 Github action 的動作,可以把觸發後的訊息改成你要的東西~
https://github.com/louis70109/line-bot-gitbub-actions-receiver
我晚點研究實作看看!!謝謝!太神了
https://github.com/taichunmin/ithelp-ironman-2023
這個是我做的自動發文機器人,是用 Node.js 開發的,並且使用 GitHub Actions,所以不用花錢就能用,也不需要使用自己的電腦自動發文喔!
用Github Actions去觸發定時排程好聰明!
把草稿內容貼CSV再去排程讀
也是一個好作法呢
就不用在那邊爬蟲爬半天XDD
我自己也比較熟悉nodeJS
我還沒有特別去研究那個token
那個token好像固定登入的cookie好像就是同一組?
所以要抓一次token、發文系列的ID
把他放到程式裡面的變數
整個30天看起來就可以直接跑起來了XD ?
上面python的我也發現一個問題
要一直開著自己電腦跑那個程式
而且用schedule.run_pending() 很容易程式當掉
目前測試主機如果休眠好像也會跑失敗
我現在是變成寫.bat檔去開python
跑那隻我獨立分出來的publishOnePost.py
自動發一篇預存的草稿
然後安排系統排程定時做... 這樣就還是需要一台自己電腦去跑XDD
不同系列好像也是一樣的 token,我在猜可能是跟 cookie 綁定的。
我目前的系列文章就全部都是用這個方式發文的喔 

有!我有追你了哈哈哈
發那個測試文章的時候會跑出提醒XDD
我現在先偷偷把.bat掛在公司電腦的系統排程(欸