小屋創作

日誌2021-07-22 17:59

Line Bot研究 - 運用google-search-results套件實現自動抓圖片以及youtube網址 更新yt網址新方法

作者:熾炎之翼

大家都知道我搞Line API應該有一個禮拜左右了
可是搞的基本上不是偏基本的 就是用來搞事沒啥實用性的功能
都不是什麼值得用來寫一篇文章的東西

但是今天就來分享一下我最近搞的酷功能
至於Line Bot要怎麼做我這次就不涉及到了
大家可以自己去爬文看看

1.偵測到發言者句尾是「.jpg」就自動去Google圖片爬照片並貼上

2.偵測到指令「!yt」就自動上網抓yt網址並貼上

因為兩者用的技術基本上相同 都是基於google-search-results套件所實現的功能
所以就合在一起講了

其實我也是昨天才認識到有google-search-results套件的存在
而且會知道這個就是因為被google給雞掰到 = =


其實一開始在做「.jpg功能」很快就做出來了
Line API傳照片只能透過連結去傳
也就是說只要有圖片連結就能傳照片了
就是很簡單很基本的爬蟲而已
實際上我高一就有做過Google圖片爬蟲了

但是後來我發現爬到的圖片就很糊
一開始我懷疑是Line給我壓縮畫質
畢竟Line API最多只能傳1MB大小的照片 會壓縮好像也不是不可能(實際上API根本沒這麼好心 ㄏㄏ
後來我才意識到我抓到的是Google圖片所生成的縮圖
既然這樣會模糊也不意外了
(這在DC機器人抓nh網站時也發生過一次 我真是不長記性 = =

但是當我再去分析網站時 發現我抓不到完整大小的圖片
就連我四年前寫的爬蟲現在也爬不到半個鳥蛋
想也知道Google肯定改了網頁架構
爬了文發現果然在一年半前左右Google針對爬蟲進行了一次架構調整
也就是說 現在圖片的真實網址是由JavaScript所生成的
使用尋常的requests套件的手法註定是無法獲取圖片

我馬上轉念想到用selenium來試看看
但是使用這套件可以說是下下策了
雖然簡單暴力 但是速度卻是非常之慢
加上我程式是架在Heroku上 並非是我自己的電腦(為了24小時無間段運行
不確定要素太多了 我實在很不打算這樣搞

看來現有的方法都行不通
只好開始研究新的方法
就是這個時候接觸了google-search-results套件
簡單看了一下功能 我馬上
pip install google-search-results

對 這個套件實在有點屌
他基本用法是這樣
from serpapi import GoogleSearch
search = GoogleSearch({"q": "coffee", "location": "Austin,Texas", "api_key": "secretKey"})
result = search.get_dict()

然後result就是由他回傳data的dict(只節錄部分)
{'search_metadata': {'id': '60f92e565fc493e862813241', 'status': 'Success', 'json_endpoint': 'https://serpapi.com/searches/ca84ecb880df1c2c/60f92e565fc493e862813241.json', 'created_at': '2021-07-22 08:37:42 UTC', 'processed_at': '2021-07-22 08:37:42 UTC', 'google_url': 'https://www.google.com/search?q=coffee&oq=coffee&sourceid=chrome&ie=UTF-8', 'raw_html_file': 'https://serpapi.com/searches/ca84ecb880df1c2c/60f92e565fc493e862813241.html', 'total_time_taken': 2.22}, 'search_parameters': {'engine': 'google', 'q': 'coffee', 'google_domain': 'google.com', 'device': 'desktop'}, 'search_information': {'organic_results_state': 'Results for exact spelling', 'total_results': 2420000000, 'time_taken_displayed': 1.04, 'query_displayed': 'coffee'}, 'local_map': {'link': 'https://www.google.com/search?q=coffee&npsic=0&rflfq=1&rldoc=1&rllag=-789449,113921330,19&tbm=lcl&sa=X&ved=2ahUKEwj0rfWao_bxAhXFHM0KHWbcA5MQtgN6BAgOEAQ', 'image': 'https://serpapi.com/searches/60f92e565fc493e862813241/images/138160cf56e903e0526b91de747b7147.png', 'gps_coordinates': {'latitude': -7.89449, 'longitude': 113.92133, 'altitude': 19}}, 'local_results': {'more_locations_link': 'https://www.google.com/search?tbs=lf:1,lf_ui:9&tbm=lcl&q=coffee&rflfq=1&num=10&sa=X&ved=2ahUKEwj0rfWao_bxAhXFHM0KHWbcA5MQjGp6BAgOEEQ', 'places': [{'position': 1, 'title': 'RASAHARSA COFEE SHOP', 'place_id': '7074646508022715519', 'lsig': 'AB86z5Vz9EVDe6jWlAXws7sRWJoH', 'place_id_search': 'https://serpapi.com/search.json?device=desktop&engine=google&google_domain=google.com&lsig=AB86z5Vz9EVDe6jWlAXws7sRWJoH&ludocid=7074646508022715519&q=coffee&tbm=lcl', 'type': 'Kedai Kopi', 'address': 'Jl. Rawamangun Muka Timur No.60, RT.3/RW.12, Rawamangun, Kec. Pulo Gadung, Kota Jakarta Timur, Daerah Khusus Ibukota Jakarta', 'thumbnail': 'https://serpapi.com/searches/60f92e565fc493e862813241/images/f843ddced0d0e6cb6390cab82d6108eb7bf1f0c6177e99d35f8c4af75440c9817a3da0f1f5e7f992.png', 'gps_coordinates': {'latitude': -0.7896234, 'longitude': 113.92133}}, {'position': 2, 'title': 'Teras Tongkrong', 'place_id': '9352390002825474361', 'lsig': 'AB86z5WVeaK9CJMwcCUFrdnOwtY_', 'place_id_search': 'https://serpapi.com/search.json?device=desktop&engine=google&google_domain=google.com&lsig=AB86z5WVeaK9CJMwcCUFrdnOwtY_&ludocid=9352390002825474361&q=coffee&tbm=lcl', 'rating': 4.1, 'reviews': 14, 'type': 'Kafe', 'address': 'Makan di tempat · Bawa pulang', 'thumbnail': 'https://serpapi.com/searches/60f92e565fc493e862813241/images/f843ddced0d0e6cb6390cab82d6108eb9abd2d91a1428592ef4c4094e354286bab2242420823eb22.jpeg', 'gps_coordinates': {'latitude': -0.789275, 'longitude': 113.921326}}

裡面就有包含我們要的結果

於是我就掌握了全新的力量方法來解決問題
接下來我會貼上程式碼並層層解釋
    if get_message[-4:].lower() == '.jpg':
        URL_list = []
        try:
            params = {
                "engine": "google",
                "tbm": "isch",
                "api_key": "Your KEY",
            }
            params['q'] = get_message
            client = GoogleSearch(params)
            data = client.get_dict()
            imgs = data['images_results']
            x = 0
            for img in imgs:
                if x < 5:
                    URL_list.append(img['original'])
                    x += 1
        except:
            url = 'https://www.google.com.tw/search?q=' + \
                get_message+'&tbm=isch'
            request = requests.get(url=url)
            html = request.content
            bsObj = BeautifulSoup(html, 'html.parser')
            content = bsObj.findAll('img', {'class': 't0fcAb'})
            for i in content:
                URL_list.append(i['src'])
        url = random.choice(URL_list)
        message = ImageSendMessage(
            original_content_url=url, preview_image_url=url
        )
        line_bot_api.reply_message(event.reply_token, message)

首先要解釋get_message
這個東西其實就是機器人收到的訊息內容
get_message = event.message.text.rstrip()
本體是event.message.text
後面的rstrip()只是我把句尾無謂的空格去掉
以免發生「.jpg    」多按到幾下空格就無法識別的情況

這邊就是當句尾是.jpg時(這邊使用lower()函式強制轉小寫來判斷 這樣就算打成大寫也不影響
程式就會執行以下的程序
我一開始放了URL_list這個空串列 用途是拿來裝待會提取到的網址

然後大家應該發現我用了try...except的方法
因為我剛剛雖然沒有提到 但是google-search-results套件存在一個致命的缺點
就是免費仔有搜尋次數限制...

對 一個月只能搜個100次
謝謝資本主義帶給我的傷害

不過只放到一、兩個群組裡面是還好
反正也不是也不能用到商業用途上

而且解決方法有很多
你也可以刷帳號去拿一大堆免費API 沒額度就一直換就好
但這個我沒還沒搞 所以今天就不討論了

我現在是先except處用舊的方式做back up(就是爬到縮圖那個
大家也能看看我之前用的傳統方法怎麼爬的

這次著重點在try裡面的寫法
大家會先看到我創了個dict叫做params
這個就是待會要餵給函式用的
這邊注意到"tbm": "isch"這部分
"isch"是指Google圖片服務
大家可以點到Google圖片的網址會看到「tbm=isch」這個小東西
tbm就是Google搜尋用來分類別的標籤

"api_key"這邊就是要填上你帳號的api_key
去google-search-results套件官網註冊帳號後會得到一個專屬api_key
額度也是靠這個算的

然後params['q'] = get_message這邊
就是在params補上搜尋關鍵字
這邊就是拿整段話當關鍵字來搜尋
其實一開始是用get_message[:-4]來去掉「.jpg」
但是之後遇到有人用到「genius.jpg」然後程式只印一個普通天才的情況

所以我就保留了.jpg 反正就算放著對所有的搜尋基本上也沒有影響
而且也能幫我過濾掉gif之類的檔案

接著餵函式以後就把搜尋資料存到變數data
接著就是要過濾資料的部分

反正圖片資料都在images_results對應的value
data['images_results']就能過濾出圖片的資訊
然後因為資料繁多 所以我用了疊代
img命名內部元素
圖片連結就在img['original']裡面

然後因為我想製造點隨機的性質
而且要是只擷取第一張有時候會不是理想中的目標
再來Line API限制只能傳輸1MB以下的圖片
要是超過就會發不出去 只抓取圖片連結根本不可能知道有沒有超過
所以運用隨機就有機會挑到符合規格的圖片(相比只抓一張 可能會抓到超過大小的圖片
所以我這邊就選取前五張(越後面會逐漸失去與關鍵字的關聯性)的網址放到URL_list
然後再用random.choice()來隨機挑選一張發出去

message = ImageSendMessage(original_content_url=url, preview_image_url=url)
line_bot_api.reply_message(event.reply_token, message)
這邊就是line bot傳圖片的格式

傳圖片就基本上這樣



然後是傳YouTube網頁連結的部分
先上個程式碼
    if get_message[:3].lower() == '!yt':
        URL_list = []
        params = {
            "engine": "google",
            "tbm": "vid",
            "api_key": "Your_KEY",
        }
        params['q'] = get_message[4:]
        client = GoogleSearch(params)
        data = client.get_dict()
        imgs = data['video_results']
        x = 0
        for img in imgs:
            if x < 1 and 'youtube' in img['link']:
                URL_list.append(img['link'])
                x += 1
        reply = TextSendMessage(text=URL_list[0])
        line_bot_api.reply_message(event.reply_token, reply)

其實大同小異
偵測到開頭是「!yt」就開始執行
params里tbm的value換成vid
對應Google影片搜尋

關鍵字就是get_message[4:] 換句話說就是「!yt」後面的內容
然後後面過濾資料就不贅述了

然後在疊代的時候
我目前是設計只擷取第一個出現的YouTube網址
這邊沒多收幾個網址是覺得沒必要
不過我還是沒有移除URL_list  算是留了一個後路給之後可能改變心意的自己

reply = TextSendMessage(text=URL_list[0])
line_bot_api.reply_message(event.reply_token, reply)
這邊就是Line API傳送文字訊息的格式

傳YouTube連結的部分也就到此為止



其實目前兩邊都還有很大的進步空間
google-search-results雖然功能超強 但是真的給免費仔限制太多
目前只能夠應付一下少量的需求
我勢必還是要找出新的方法來實現Google服務的爬蟲

再來影片連結這部分其實有點繞遠路
老實說要不是可以順便一起來講 不然我也不會這樣用
等我之後研究YouTube API不用使用這有額度的方法了
現在只是先頂著用而已(雖然真的很無腦又很好用XDDDD

更:
摸了YouTube API後改寫成這樣了
if get_message[:3].lower() == '!yt':
        YOUTUBE_API_KEY = jdata['YOUTUBE_API_KEY']
        q = get_message[4:]
        url = 'https://www.googleapis.com/youtube/v3/search?part=snippet&q=' + \
            q+'&key='+YOUTUBE_API_KEY+'&type=video&maxResults=1'
        request = requests.get(url)
        data = request.json()
        URL = 'https://www.youtube.com/watch?v=' + \
            data['items'][0]['id']['videoId']
        reply = TextSendMessage(text=URL)
        line_bot_api.reply_message(event.reply_token, reply)

結合YOUTUBE_API_KEY(我這邊直接存在json裡了 如果不存就直接把這一串打上去)
然後這邊需要一個特別的訪問網址
「q」是關鍵字 「key」是你的API KEY  「maxResults」是最大搜尋結果數(最多為20)
然後把這些丟給requests
然後就會爬出如下的資訊(以LoveLive為例,節錄)
{\n  "kind": "youtube#searchListResponse",\n  "etag": "2CTo31NZZ8LEp4XhzJh8e6anA78",\n  "nextPageToken": "CBQQAA",\n  "regionCode": "TW",\n  "pageInfo": {\n    "totalResults": 1000000,\n    "resultsPerPage": 20\n  },\n  "items": [\n    {\n      "kind": "youtube#searchResult",\n      "etag": "nkf-tdZqiRsIyZpYUJxZDHfCNxc",\n      "id": {\n        "kind": "youtube#video",\n        "videoId": "k2OX38jCfgg"\n      },\n      "snippet": {\n        "publishedAt": "2021-07-01T14:00:09Z",\n        "channelId": "UCgdwtyqBunlRb-i-7PnCssQ",\n        "title": "Love Live! \xe4\xb8\x80\xe6\x9c\x9f \xe7\xac\xac01\xe8\xa9\xb1\xe3\x80\x90\xe5\xaf\xa6\xe7\x8f\xbe\xe5\x90\xa7!\xe6\x88\x91\xe5\x80\x91\xe7\x9a\x84\xe5\xa4\xa2\xe6\x83\xb3\xe3\x80\x91\xef\xbd\x9cMuse\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1 \xe5\x8b\x95\xe7\x95\xab \xe7\xb7\x9a\xe4\xb8\x8a\xe7\x9c\x8b",\n        "description": "\xe3\x80\x8aLove Live!\xe3\x80\x8b 7/01(\xe5\x9b\x9b)\xe8\xb5\xb7\xef\xbc\x8c\xe6\xaf\x8f\xe5\xa4\xa9\xe6\x99\x9a\xe4\xb8\x8a10\xef\xbc\x9a00\xe6\x9b\xb4\xe6\x96\xb0   \xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1YouTube\xe9\xa0\xbb\xe9\x81\x93\xe9\xa6\x96\xe6\x92\xad \xe3\x80\x90MUSE\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1\xe6\xa8\x82\xe5\x9c\x92\xe3\x80\x91\xe2\x9c\xa8\xe5\x85\xa8\xe6\x96\xb0\xe8\xb3\xbc\xe7\x89\xa9\xe5\xae\x98\xe7\xb6\xb2/APP\xe6\xad\xa3\xe5\xbc\x8f\xe5\x95\x9f\xe7\x94\xa8\xe5\x85\x8d\xe5\x87\xba\xe9\x96\x80\xe5\x8b\x95\xe6\xbc\xab ...",\n        "thumbnails": {\n          "default": {\n            "url": "https://i.ytimg.com/vi/k2OX38jCfgg/default.jpg",\n            "width": 120,\n            "height": 90\n          },\n          "medium": {\n            "url": "https://i.ytimg.com/vi/k2OX38jCfgg/mqdefault.jpg",\n            "width": 320,\n            "height": 180\n          },\n          "high": {\n            "url": "https://i.ytimg.com/vi/k2OX38jCfgg/hqdefault.jpg",\n            "width": 480,\n            "height": 360\n          }\n        },\n        "channelTitle": "Muse\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1-TW",\n        "liveBroadcastContent": "none",\n        "publishTime": "2021-07-01T14:00:09Z"\n      }\n    },\n    {\n      "kind": "youtube#searchResult",\n      "etag": "-f1aWepXgJq8mfd9idUvOlopIVw",\n      "id": {\n        "kind": "youtube#video",\n        "videoId": "3Ix2gzd0kAc"\n      },\n      "snippet": {\n        "publishedAt": "2021-07-19T14:00:11Z",\n        "channelId": "UCgdwtyqBunlRb-i-7PnCssQ",\n        "title": "Love Live! \xe4\xba\x8c\xe6\x9c\x9f \xe7\xac\xac06\xe8\xa9\xb1\xe3\x80\x90\xe8\x90\xac\xe8\x81\x96\xe7\xaf\x80\xe5\xbf\xab\xe6\xa8\x82\xe3\x80\x91\xef\xbd\x9cMuse\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1 \xe5\x8b\x95\xe7\x95\xab \xe7\xb7\x9a\xe4\xb8\x8a\xe7\x9c\x8b",\n        "description": "\xe3\x80\x8aLove Live!\xe3\x80\x8b 7/01(\xe5\x9b\x9b)\xe8\xb5\xb7\xef\xbc\x8c\xe6\xaf\x8f\xe5\xa4\xa9\xe6\x99\x9a\xe4\xb8\x8a10\xef\xbc\x9a00\xe6\x9b\xb4\xe6\x96\xb0   \xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1YouTube\xe9\xa0\xbb\xe9\x81\x93\xe9\xa6\x96\xe6\x92\xad \xe3\x80\x90MUSE\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1\xe6\xa8\x82\xe5\x9c\x92\xe3\x80\x91\xe2\x9c\xa8\xe5\x85\xa8\xe6\x96\xb0\xe8\xb3\xbc\xe7\x89\xa9\xe5\xae\x98\xe7\xb6\xb2/APP\xe6\xad\xa3\xe5\xbc\x8f\xe5\x95\x9f\xe7\x94\xa8\xe5\x85\x8d\xe5\x87\xba\xe9\x96\x80\xe5\x8b\x95\xe6\xbc\xab ...",\n        "thumbnails": {\n          "default": {\n            "url": "https://i.ytimg.com/vi/3Ix2gzd0kAc/default.jpg",\n            "width": 120,\n            "height": 90\n          },\n          "medium": {\n            "url": "https://i.ytimg.com/vi/3Ix2gzd0kAc/mqdefault.jpg",\n            "width": 320,\n            "height": 180\n          },\n          "high": {\n            "url": "https://i.ytimg.com/vi/3Ix2gzd0kAc/hqdefault.jpg",\n            "width": 480,\n            "height": 360\n          }\n        },\n        "channelTitle": "Muse\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1-TW",\n        "liveBroadcastContent": "none",\n        "publishTime": "2021-07-19T14:00:11Z"\n      }\n    },\n    {\n      "kind": "youtube#searchResult",\n      "etag": "nwvzRowcQj9lwLSISwoAofvjvio",\n      "id": {\n        "kind": "youtube#video",\n        "videoId": "DobT0FEyZDA"\n      },\n      "snippet": {\n        "publishedAt": "2021-07-18T14:00:10Z",\n        "channelId": "UCgdwtyqBunlRb-i-7PnCssQ",\n        "title": "Love Live! \xe4\xba\x8c\xe6\x9c\x9f \xe7\xac\xac05\xe8\xa9\xb1\xe3\x80\x90\xe5\x85\xa8\xe6\x96\xb0\xe7\x9a\x84\xe6\x88\x91\xe3\x80\x91\xef\xbd\x9cMuse\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1 \xe5\x8b\x95\xe7\x95\xab \xe7\xb7\x9a\xe4\xb8\x8a\xe7\x9c\x8b",\n        "description": "\xe3\x80\x8aLove Live!\xe3\x80\x8b 7/01(\xe5\x9b\x9b)\xe8\xb5\xb7\xef\xbc\x8c\xe6\xaf\x8f\xe5\xa4\xa9\xe6\x99\x9a\xe4\xb8\x8a10\xef\xbc\x9a00\xe6\x9b\xb4\xe6\x96\xb0   \xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1YouTube\xe9\xa0\xbb\xe9\x81\x93\xe9\xa6\x96\xe6\x92\xad \xe3\x80\x90MUSE\xe6\x9c\xa8\xe6\xa3\x89\xe8\x8a\xb1\xe6\xa8\x82\xe5\x9c\x92\xe3\x80\x91\xe2\x9c\xa8\xe5\x85\xa8\xe6\x96\xb0\xe8\xb3\xbc\xe7\x89\xa9\xe5\xae\x98\xe7\xb6\xb2/APP\xe6\xad\xa3\xe5\xbc\x8f\xe5\x95\x9f\xe7\x94\xa8\xe5\x85\x8d\xe5\x87\xba\xe9\x96\x80\xe5\x8b\x95\xe6\xbc\xab ...",\n        "thumbnails": {\n          "default": {\n            "url": "https://i.ytimg.com/vi/DobT0FEyZDA/default.jpg",\n            "width": 120,\n            "height": 90\n          },\n          "medium": {\n            "url": "https://i.ytimg.com/vi/DobT0FEyZDA/mqdefault.jpg",\n  

如果直接去那網址來看就是這樣

然後為了便於之後的使用
這邊就用json()函式來轉成json形式
之後就是簡單的處理就能得到VideoId
然後拼接一下就是影片的網址了


這次研究就差不多就這樣
把新的發現+程式講解搞一起 每次都要超多字 = =
不過有新的酷功能我會再繼續分享的

感謝觀看

39

13

LINE 分享

相關創作

狂三腋下緊急修正公告!

【柴柴 f.t 疤疤】柴柴的生日充滿甜滋滋~汪

回去玩了一下Bang Dream測試手機性能遽然抽到5星了w

留言

開啟 APP

face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】