PTT Library 爬蟲教學
注意,此篇教學僅適用於 PTT Library 0.7 以下,已經是過時的教學。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
新版本開發指南: https://hackmd.io/@CodingMan/PyPttManual
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
如果你需要大量下載 PTT 文章並且使用 Python,這篇教學將會一步一步地引導你寫出你專屬的 PTT 爬蟲。
適用於 Python 初新者以上。
Step 0 安裝環境
你需要安裝 Python 3 最新版本,請至此處下載 https://www.python.org/。
接下來安裝用來操作 PTT 的函式庫 PTT Library。
pip install PTTLibrary
或者加個 --upgrade 來更新 PTT Library。
pip install PTTLibrary --upgrade
Step 1 登入 PTT
首先這是一個最基本的 PTT 登入登出範例,接下來的步驟都會以這個範例慢慢演進。
import sys
from PTTLibrary import PTT
ID = '你的帳號'
Password = '你的密碼'
# 登入
ErrCode = PTTBot.login(ID, Password)
# 使用錯誤碼,判斷登入是否成功
if ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('登入失敗')
sys.exit()
# 登出
PTTBot.logout()
如果你不希望機器人登入的時候,把其他登入一起踢掉,你可以加入一個參數 kickOtherLogin=False 像這樣
PTTBot = PTT.Library(kickOtherLogin=False)
如此一來,範例就變成
import sys
from PTTLibrary import PTT
ID = '你的帳號'
Password = '你的密碼'
# 如果不想在登入的時候踢掉其他登入,你可以這樣使用
PTTBot = PTT.Library(kickOtherLogin=False)
# 登入
ErrCode = PTTBot.login(ID, Password)
# 使用錯誤碼,判斷登入是否成功
if ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('登入失敗')
sys.exit()
# 登出
PTTBot.logout()
如果你不想在程式碼裡寫死你的帳號密碼,你可以用以下程式碼從 Account.txt 讀入賬密,在 commit 時記得 ignore 或者刪除即可。而檔案內容則需要輸入。
{"ID":"YourID", "Password":"YourPW"}
如果檔案不存在或有錯誤,則會請你從螢幕輸入帳號密碼。
import json
import getpass
try:
with open('Account.txt') as AccountFile:
Account = json.load(AccountFile)
ID = Account['ID']
Password = Account['Password']
except FileNotFoundError:
ID = input('請輸入帳號: ')
Password = getpass.getpass('請輸入密碼: ')
如果把這段程式碼,結合了範例就會變成這樣。
import sys
import json
import getpass
from PTTLibrary import PTT
try:
with open('Account.txt') as AccountFile:
Account = json.load(AccountFile)
ID = Account['ID']
Password = Account['Password']
except FileNotFoundError:
ID = input('請輸入帳號: ')
Password = getpass.getpass('請輸入密碼: ')
# 如果不想在登入的時候踢掉其他登入,你可以這樣使用
PTTBot = PTT.Library(kickOtherLogin=False)
# 登入
ErrCode = PTTBot.login(ID, Password)
# 使用錯誤碼,判斷登入是否成功
if ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('登入失敗')
sys.exit()
# 登出
PTTBot.logout()
到目前為止,我們就完成了有關登入的範例。
Step 2 爬文
我們在上一個步驟完成了,有關登入的教學,接下來開始爬文。
先介紹單一文章的擷取 API getPost,它的使用方法是長這樣的。
ErrCode, Post = PTTBot.getPost('某個板', PostIndex=文章編號)
我們可以看到他會回傳錯誤碼跟一篇文章回來,如果錯誤碼為 0 即為成功,接下來我們來解析一下如何使用回傳回來的文章資料結構。
API | 說明 |
getBoard | 文章所在的版 (Wanted) |
getID | 文章 ID ex: 1PCBfel1 |
getAuthor | 作者 |
getDate | 文章發布時間 |
getTitle | 文章標題 |
getContent | 文章內文 |
getMoney | 文章 P 幣 |
getWebUrl | 文章網址 |
getPushList | 文章即時推文清單 |
getOriginalData | 文章原始資料 (備份色碼用) |
其中,getPushList 可以取得文章的推文清單,同樣地我們也來列出所有推文資訊。
getType | 推文類別 推噓箭頭 |
getAuthor | 推文 ID |
getContent | 推文內文 |
getIP | 推文IP (如果存在) |
getTime | 推文時間 |
接下來我們可以把將文章資料拿出來的功能寫成一個函式方便使用。
def showPost(Post):
PTTBot.Log('文章代碼: ' + Post.getID())
PTTBot.Log('作者: ' + Post.getAuthor())
PTTBot.Log('標題: ' + Post.getTitle())
PTTBot.Log('時間: ' + Post.getDate())
PTTBot.Log('價錢: ' + str(Post.getMoney()))
PTTBot.Log('IP: ' + Post.getIP())
PTTBot.Log('網址: ' + Post.getWebUrl())
PTTBot.Log('內文:\n' + Post.getContent())
PushCount = 0
BooCount = 0
ArrowCount = 0
for Push in Post.getPushList():
if Push.getType() == PTT.PushType.Push:
PushCount += 1
elif Push.getType() == PTT.PushType.Boo:
BooCount += 1
elif Push.getType() == PTT.PushType.Arrow:
ArrowCount += 1
Author = Push.getAuthor()
Content = Push.getContent()
# IP = Push.getIP()
PTTBot.Log('推文: ' + Author + ': ' + Content)
PTTBot.Log('共有 ' + str(PushCount) + ' 推 ' + str(BooCount) + ' 噓 ' + str(ArrowCount) + ' 箭頭')
現在是時候,將這些程式碼整到我們的範例試試看了,在下面的範例,我用 Wanted 版當作範例,並取得文章編號 6000 的文章。
import sys
import json
import getpass
from PTTLibrary import PTT
def showPost(Post):
PTTBot.Log('文章代碼: ' + Post.getID())
PTTBot.Log('作者: ' + Post.getAuthor())
PTTBot.Log('標題: ' + Post.getTitle())
PTTBot.Log('時間: ' + Post.getDate())
PTTBot.Log('價錢: ' + str(Post.getMoney()))
PTTBot.Log('IP: ' + Post.getIP())
PTTBot.Log('網址: ' + Post.getWebUrl())
PTTBot.Log('內文:\n' + Post.getContent())
PushCount = 0
BooCount = 0
ArrowCount = 0
for Push in Post.getPushList():
PushType = Push.getType()
if PushType == PTT.PushType.Push:
PushCount += 1
elif PushType == PTT.PushType.Boo:
BooCount += 1
elif PushType == PTT.PushType.Arrow:
ArrowCount += 1
Author = Push.getAuthor()
Content = Push.getContent()
# IP = Push.getIP()
PTTBot.Log('推文: ' + Author + ': ' + Content)
PTTBot.Log('共有 ' + str(PushCount) + ' 推 ' + str(BooCount) + ' 噓 ' + str(ArrowCount) + ' 箭頭')
try:
with open('Account.txt') as AccountFile:
Account = json.load(AccountFile)
ID = Account['ID']
Password = Account['Password']
except FileNotFoundError:
ID = input('請輸入帳號: ')
Password = getpass.getpass('請輸入密碼: ')
# 如果不想在登入的時候踢掉其他登入,你可以這樣使用
PTTBot = PTT.Library(kickOtherLogin=False)
# 登入
ErrCode = PTTBot.login(ID, Password)
# 使用錯誤碼,判斷登入是否成功
if ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('登入失敗')
sys.exit()
ErrCode, Post = PTTBot.getPost('Wanted', PostIndex=6000)
if ErrCode == PTT.ErrorCode.PostDeleted:
if Post.getDeleteStatus() == PTT.PostDeleteStatus.ByAuthor:
PTTBot.Log('文章被原 PO 刪掉了')
elif Post.getDeleteStatus() == PTT.PostDeleteStatus.ByModerator:
PTTBot.Log('文章被版主刪掉了')
PTTBot.Log('作者: ' + Post.getAuthor())
sys.exit()
elif ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('取得文章詳細資訊失敗 錯誤碼: ' + str(ErrCode))
sys.exit()
# 登出
PTTBot.logout()
這時候,你可能會想如果想要爬最新的一篇文章怎麼辦? 這時候我們則需要 getNewestIndex 這支API
ErrCode, NewestIndex = PTTBot.getNewestIndex(Board=Board)
NewestIndex 就會是該版的最新文章編號。
另外,如果需要搜尋條件,也是沒問題。PTT Library 支援所有搜尋形式。
Keyword | 搜尋關鍵字 |
Author | 搜尋作者 |
Push | 搜尋推文數 |
Mark | 搜尋標記 m or s |
Money | 搜尋稿酬 |
使用方法如下,要特別注意的是搜尋形式跟搜尋條件都要一起代給 getNewestIndex 與 getPost 喔,部分程式碼如下。
Board = 'Wanted'
# 搜尋類別
inputSearchType = PTT.PostSearchType.Keyword
# 搜尋條件
inputSearch = '公告'
ErrCode, NewestIndex = PTTBot.getNewestIndex(Board=Board, SearchType=inputSearchType, Search=inputSearch)
if ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('取得 ' + Board + ' 板最新文章編號失敗')
sys.exit()
ErrCode, Post = PTTBot.getPost(Board, PostIndex=NewestIndex, SearchType=inputSearchType, Search=inputSearch)
if ErrCode == PTT.ErrorCode.PostDeleted:
if Post.getDeleteStatus() == PTT.PostDeleteStatus.ByAuthor:
PTTBot.Log('文章被原 PO 刪掉了')
elif Post.getDeleteStatus() == PTT.PostDeleteStatus.ByModerator:
PTTBot.Log('文章被版主刪掉了')
PTTBot.Log('作者: ' + Post.getAuthor())
sys.exit()
elif ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('取得文章詳細資訊失敗 錯誤碼: ' + str(ErrCode))
sys.exit()
最後的最後,終於要進入最厲害的 API 了,crawlBoard。
此 API 適合一次爬行大量文章,並可自訂處理方式,其中 PostHandler 就是你可以自訂處理文章的地方,每爬到一篇文章,PostHandler 就會被呼叫一次,你可以根據 showPost 取出任何你需要的文章資訊,做存檔或者顯示。
import sys
import json
import getpass
import codecs
from PTTLibrary import PTT
def showPost(Post):
PTTBot.Log('文章代碼: ' + Post.getID())
PTTBot.Log('作者: ' + Post.getAuthor())
PTTBot.Log('標題: ' + Post.getTitle())
PTTBot.Log('時間: ' + Post.getDate())
PTTBot.Log('價錢: ' + str(Post.getMoney()))
PTTBot.Log('IP: ' + Post.getIP())
PTTBot.Log('網址: ' + Post.getWebUrl())
PTTBot.Log('內文:\n' + Post.getContent())
PushCount = 0
BooCount = 0
ArrowCount = 0
for Push in Post.getPushList():
PushType = Push.getType()
if PushType == PTT.PushType.Push:
PushCount += 1
elif PushType == PTT.PushType.Boo:
BooCount += 1
elif PushType == PTT.PushType.Arrow:
ArrowCount += 1
Author = Push.getAuthor()
Content = Push.getContent()
# IP = Push.getIP()
PTTBot.Log('推文: ' + Author + ': ' + Content)
PTTBot.Log('共有 ' + str(PushCount) + ' 推 ' + str(BooCount) + ' 噓 ' + str(ArrowCount) + ' 箭頭')
def PostHandler(Post):
with codecs.open("CrawlBoardResult.txt", "a", "utf-8") as ResultFile:
ResultFile.write('P幣: ' + str(Post.getMoney()) + ' ' + Post.getTitle() + '\n')
try:
with open('Account.txt') as AccountFile:
Account = json.load(AccountFile)
ID = Account['ID']
Password = Account['Password']
except FileNotFoundError:
ID = input('請輸入帳號: ')
Password = getpass.getpass('請輸入密碼: ')
# 如果不想在登入的時候踢掉其他登入,你可以這樣使用
PTTBot = PTT.Library(kickOtherLogin=False)
# 登入
ErrCode = PTTBot.login(ID, Password)
# 使用錯誤碼,判斷登入是否成功
if ErrCode != PTT.ErrorCode.Success:
PTTBot.Log('登入失敗')
sys.exit()
CrawPost = 100
EnableSearchCondition = False
inputSearchType = PTT.PostSearchType.Keyword
inputSearch = '[公告]'
if EnableSearchCondition:
ErrCode, NewestIndex = PTTBot.getNewestIndex(Board='Wanted', SearchType=inputSearchType, Search=inputSearch)
else:
ErrCode, NewestIndex = PTTBot.getNewestIndex(Board='Wanted')
if ErrCode == PTT.ErrorCode.Success:
PTTBot.Log('取得 ' + 'Wanted' + ' 板最新文章編號成功: ' + str(NewestIndex))
else:
PTTBot.Log('取得 ' + 'Wanted' + ' 板最新文章編號失敗')
sys.exit()
# MaxMultiLogin 多重登入數量
# SearchType 搜尋種類
# Search 搜尋條件
if EnableSearchCondition:
ErrCode, SuccessCount, DeleteCount = PTTBot.crawlBoard('Wanted', PostHandler, StartIndex=NewestIndex - CrawPost + 1, EndIndex=NewestIndex, SearchType=inputSearchType, Search=inputSearch)
else:
ErrCode, SuccessCount, DeleteCount = PTTBot.crawlBoard('Wanted', PostHandler, StartIndex=NewestIndex - CrawPost + 1, EndIndex=NewestIndex)
if ErrCode == PTT.ErrorCode.Success:
PTTBot.Log('爬行成功共 ' + str(SuccessCount) + ' 篇文章 共有 ' + str(DeleteCount) + ' 篇文章被刪除')
# 登出
PTTBot.logout()
如果有任何使用上的問題歡迎留言或者在 giiter 上留言,我會用最快的速度幫助你。
如果想了解其他功能,可以參考其他文章,或者直接到 PTT Library 頁面的 Test.py 有非常詳細的範例。
CodingMan,Bug maker
Github: https://github.com/PttCodingMan