# Python 爬蟲結合 LINE Notify 推播591租屋網資訊
###### tags: `Python` `LINE Notify` `GitHub` `Heroku`
## 緣起
感謝[超簡單一鍵推播 591 租屋資訊完全免 Coding-透過 Google Sheet 與 LINE Notify](https://ithelp.ithome.com.tw/articles/10255573)這篇文章,讓我有靈感及動力嘗試做一個LINE推播!
也很剛好租屋也快到期,但總是會忘記或懶得每天上591看有沒有合適的房子,於是專屬於我需求的LINE Notify通知就此誕生。
一直以來都很想寫些技術文章,或是將自己的筆記整理成一篇文章,剛好也透過這次來練練手。
## 目標
1. 抓取591租屋網符合條件且為3小時內更新的資訊
2. 每三小時執行一次程式,並透過LINE Notify推播更新的資訊
## 建置環境
- Python 3.9.2
- GitHub
- Heroku
## 申請 LINE Notify 權杖
[LINE Notify](https://notify-bot.line.me/zh_TW/)是LINE官方的帳號,只要與其他網路服務完成連動設定,就可透過LINE Notify發送訊息。
登入後,進入個人頁面。

點選發行權杖。

填寫LINE Notify名稱及要連動的群組或選擇透過一對一聊天發送,完成後點選發行。
<span style="color:#B5495B">如果是與群組連動,記得要將LINE Notify邀請至群組。</span>

發行成功,會給你一串權杖,將這串權杖記錄起來。

連動成功,如果有 LINE Notify 的好友,LINE會通知連動設定完成。

成功連動的服務,可於個人頁面**已連動的服務**察看到

## Python 爬蟲
LINE Notify設定好之後,就開始爬蟲吧!
目標:爬取591租屋資訊!
進入[591租屋](https://rent.591.com.tw/?kind=0®ion=8)頁面,依照自己需求設定條件。

F12 開啟開發者工具,到 Network 頁籤,找到想要爬取的頁面。

從 Headers 頁籤查看要抓取的 Url 及 Request Haders。

利用 Request 套件建立HTTP請求。
```` Python
# 取得591租屋資訊
import requests
#要抓取頁面的Url
url = "https://rent.591.com.tw/?kind=0®ion=8§ion=98,102,101,99,100&rentprice=0,15000&pattern=2&order=posttime&orderType=desc"
#自訂 Request Headers
headers = {
"Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding" : "gzip, deflate, br",
"Accept-Language" : "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Connection" : "keep-alive",
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
"Upgrade-Insecure-Requests" : "1",
"Cache-Control" : "max-age=0",
"Host" : "rent.591.com.tw",
"Cookie" : "urlJumpIp=8; urlJumpIpByTxt=%E5%8F%B0%E4%B8%AD%E5%B8%82; is_new_index=1; is_new_index_redirect=1; T591_TOKEN=0mgp6gnmca0m1aes0a653qpk76; _ga=GA1.3.1853129893.1614755590; tw591__privacy_agree=0; _ga=GA1.4.1853129893.1614755590; _fbp=fb.2.1614755592267.503379817; new_rent_list_kind_test=0; _gid=GA1.3.990458239.1615170698; _gid=GA1.4.990458239.1615170698; webp=1; PHPSESSID=ugspv0rqvnetihun53ane0jlc4; XSRF-TOKEN=eyJpdiI6ImloZzR5Qm9SRk1XNVd4bmJ2VG8zNUE9PSIsInZhbHVlIjoiSExCSnRITEZjSE8rWktjVEptSnlEd1AxNEs1cHRcL1dEYktOR0dvUUNwdU9vNVVPUHlaK3UyXC9pOWpCVElxV0JJdzZGWFF0bytcL3MrSGNGSlpyQk96OGc9PSIsIm1hYyI6IjQ5NDgzZjc1YWExYTkyZDQ2YWRjZWQwZDI5YTIwODZhMTJkYzNlMmZiYzUwNmZmMzY2YjNhZjQ4NGI4OGY2NjMifQ%3D%3D; 591_new_session=eyJpdiI6ImpYUE9QWDJWYVwvaVlJc3dUK0ZiY3h3PT0iLCJ2YWx1ZSI6ImVMYnpSQ2ZhNG9VZHNSdWZNMjZTSG5nUTZOaWZlZ05kQkRXVkNLZDAxQlBqWWJneXVZbXZEWmd6SVRrMU5ZbGtrOU9tVG9RZm1CM2ZKUnNYQVlJaTNRPT0iLCJtYWMiOiIwN2UzODgzYWE0OGM2YTlkMDI1YTVjYjkzNmUyYWJiMzA5M2JmN2M0M2Q4NDQ1ODhlYTZkM2E3NzFkMjVjMWZlIn0%3D"
}
response = requests.get(url=url, headers=headers)
print(response)
#### 產生結果
#### <Response [200]>
#### Response Status Code 200,代表成功
````
引用 BeautifulSoup 傳入回傳的HTML。
```` Python
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
# 輸出排版後的HTML
print(soup.prettify())
````
執行結果片段
``` html
<div class="sub-nav-list nav-wide">
<dl>
<dt>
待租房源
</dt>
<dd>
<a google-data-stat="頭部導航_租屋_所有房源" href="//rent.591.com.tw">
所有房源
</a>
<a google-data-stat="頭部導航_租屋_房東出租" href="//rent.591.com.tw?shType=host">
房東出租
</a>
<a google-data-stat="頭部導航_租屋_整層住家" href="//rent.591.com.tw?kind=1">
整層住家
</a>
<a google-data-stat="頭部導航_租屋_地圖找房" href="//rent.591.com.tw/map-index.html">
地圖找房
</a>
<a google-data-stat="頭部導航_租屋_獨立套房" href="//rent.591.com.tw?kind=2">
獨立套房
</a>
<a google-data-stat="頭部導航_租屋_捷運找房" href="//rent.591.com.tw?mrt=1">
捷運找房
</a>
<a google-data-stat="頭部導航_租屋_分租套房" href="//rent.591.com.tw?kind=3">
分租套房
</a>
<a google-data-stat="頭部導航_租屋_學校找房" href="//rent.591.com.tw?school=0">
學校找房
</a>
<a google-data-stat="頭部導航_租屋_雅房" href="//rent.591.com.tw?kind=4">
雅房
</a>
</dd>
</dl>
</div>
```
成功取得 HTML 結構後,就可以繼續利用 BeautifulSoup 擷取所需的資訊。


```` Ptython
#正則
import re
# 取得 <ul class="listInfo clearfix"></ul> 內所有元素
listInfoUl = soup.find_all("ul", class_="listInfo clearfix")
num = 0
for ul in listInfoUl:
# 照片
img = ul.find("img").get("data-original")
# 標題
title = ul.find("a").getText()
# 詳細資訊的 URL
detailUrl = ul.find("a").get("href")
# 價格
price = ul.find("div", class_="price").getText().strip()
# 簡易說明
wordDetail = ''
for de in ul.find_all("p", class_="lightBox"):
wordDetail = wordDetail + " | " + de.getText().replace(" ", "").replace("\n", "")
# 更新時間點
for up in ul.find_all("em"):
pattern = re.compile('更新')
if len(pattern.findall(up.getText())) > 0:
uptime = up.getText()
#印出擷取結果
print(
'title: ' + title + ", " +
'img: ' + img + "," +
'detailUrl:' + detailUrl + ", " +
'price: ' + price + ", " +
'detail:' + wordDetail + ", " +
'update' + uptime
)
print("--------------")
````
執行結果片段

取得3小時內更新的內容,並製作LINE要顯示的內容。
```` Python
#表情符號
import emoji
#取得3小時內更新的內容
pattern = re.compile('小時內更新')
if len(pattern.findall(uptime)) > 0:
pattern = re.compile('(.*)(?=小時)')
hours = re.search(pattern, uptime).group(1)
if int(hours) <= 3:
#LINE訊息
msg = emoji.emojize('\n小幫手來啦~ :relaxed: \n租屋網更新資訊啦! :boom: \n :mega: ', use_aliases=True) + title + emoji.emojize('\n :dollar: ', use_aliases=True) + price + emoji.emojize('\n :memo: ', use_aliases=True) + wordDetail + emoji.emojize('\n :alarm_clock: ', use_aliases=True) + uptime + emoji.emojize('\n\n :tada: 看更詳細點↓網址 \n https:', use_aliases=True) + detailUrl
#印出要傳送的LINE訊息
print(msg)
print('-------------')
````
執行結果片段

透過 LINE Notify API 送出訊息。
```` Python
#lineNotify設定
def lineNotifyMessage(token, msg, imgUrl):
# hearders 這兩項必帶
# token 為 LINE Notinfy 申請的權杖
headers = {
"Authorization": "Bearer " + token,
"Content-Type": "application/x-www-form-urlencoded"
}
# message : 要顯示的文字
# imageThumbnail、imageFullsize : 要顯示的圖片
# stickerPackageId、stickerId : 貼圖
message = {'message': msg, 'imageThumbnail':imgUrl,'imageFullsize':imgUrl,'stickerPackageId':1,'stickerId':13}
#透過 POST 傳送
req = requests.post("https://notify-api.line.me/api/notify", headers = headers, data = message)
return req.status_code
# 傳送LINE訊息
lineNotifyMessage("申請的權杖", msg, img)
````
執行結果

## Git檔案至 GitHub
建立 Repository,輸入Repository Name,將權限設為公開(Public)。

將程式碼上傳至剛剛建立的Repository。
```
RentHouseInfo.py #主程式
requirements.txt #告訴Heroku要安裝什麼套件
runtime.txt #告訴Heroku Python的版本
```

## Heroku架設
[Heroku](https://www.heroku.com/)是一個平台即服務(PaaS),可自行在Heroku平台開發和佈署各種網站,它提供免費帳戶一個月一定小時的運行時間,使用量不大的話,覺得滿划算的。
建立帳號後,建立一個應用程式。

輸入名稱。

選擇 Deploy 頁籤,再Deployment method 選擇 GitHub,然後點選 Connect to GitHub 按鈕。

會跳出是否要授權Heroku與GitHub之間連動。

完成連接後輸入你的Repository。

自動部屬。

切換至Resources頁籤。

在Add-ons區塊中搜尋Heroku Scheduler。

新增Heroku Schedule Add-on。

新增完成後,點擊Heroku Scheduler,進入設定頁面。

點擊 Create job 按鈕,建立一個工作。

依照自己的需求設定。

<span style="color:#B5495B">Schedule只能設定每10分鐘、每小時即每天的某個時間點,且時間點為UTC時間,如果對於時間點明確需要的需自行換算時間。</span>
現在想要每三小時執行一次,所以設定每天某個時間點執行,間格為三小時。

設定完成後,等待時間到,觀察是否有正常執行。



## 參考資料
- [超簡單一鍵推播 591 租屋資訊完全免 Coding-透過 Google Sheet 與 LINE Notify](https://ithelp.ithome.com.tw/articles/10255573)
- [使用LINE Notify發送訊息(Heroku+GitHub+Python)](https://rnnnnn.medium.com/%E4%BD%BF%E7%94%A8line-notify%E7%99%BC%E9%80%81%E8%A8%8A%E6%81%AF-heroku-github-python-9132ff9ebe1b)
- [自建 LINE Notify 訊息通知](https://www.oxxostudio.tw/articles/201806/line-notify.html)
- [LINE Notify 入門到進階應用(4) --- 傳送文字網路圖片到Line Notify 其他語言](http://white5168.blogspot.com/2017/01/line-notify-4-line-notify.html#.YD9CM9x-W01)
- [Heroku - 自動執行python腳本](https://yeeinhole.github.io/2020/03/20/heroku-trelloXline/)
- [Python emoji Packages](https://pypi.org/project/emoji/)
- [Python emoji Charts](https://www.webfx.com/tools/emoji-cheat-sheet/)