Python 爬蟲(詳細版) === --- ## 今天要做什麼 ? - 學習很多東東 ---- ![](https://i.imgur.com/yNNuuhl.png) --- ## 你會學到的東西 ? - 基礎的網頁知識 - 基礎的Html知識 - 基礎的python - Python 爬蟲 --- ## 啊這到底是可以幹嘛啦? --- ## 開始 ---- ### What is Html ? 在開始爬蟲的介紹之前 我們需要知道html是什麼 ---- 基本上就是你打開網頁看到的東東啦~ 專業一點來說的話 Html的全名是**H**yper **T**ext **M**arkup **L**anguage 也就是規範一個網頁的架構所需要的語言 ---- 我們可以來看一下基礎的 html 長的樣子 ![](https://i.imgur.com/s4Qw3gN.png) ---- ### How Can I Get It ? ---- 這裡我們先提一下網頁的基礎 當你在瀏覽器輸入一段網址 瀏覽器所幫你做的事情 是去向那個網址發送一個請求 就是所謂的GET ---- ![](https://i.imgur.com/xpkqhSA.jpg) ---- ![](https://i.imgur.com/nPGJsyE.png) ---- 當請求完畢的時候 瀏覽器就會把拿到的html轉換成你看到的樣子啦 ---- ### Then ? ---- 所以你就可以發現 如果我想爬取網頁上的東東 那我就GET他的html 然後再把我想要的部份取出來就好了啊~ ---- 沒錯 就是這麼簡單 --- ## Learn Python ---- - Hello World ```python print('Hello World') ``` ---- - 宣告變數 一個變數就像是一個容器,可以將資料存放在裡面 ![](https://i.imgur.com/OxI0Yes.jpg) ---- - `=`賦值 把東西裝進箱子裡面! ![](https://i.imgur.com/BdewN7i.png) ---- 把右邊的值給左邊的東西 ```python x = 1 print(x) y = 3 print(y) x = y print(x) ``` ---- 不同的資料型態 - integer(整數)跟string(字串) ```python x = 1 # 數字(integer) ##註解 print(x) s = 'string' # 字串(string) ##就是文字 print(s) I_have_a = "pen" I_have_an = "apple" print(I_have_an + I_have_a) #string 的加法就是在前面一個string的尾巴加上後面一個string ``` ---- - boolean(布林) 對或是錯 ```python a = True # 布林(boolean) b = False ``` ---- - list(列表) ![](https://i.imgur.com/KMc73iO.jpg) list就像是個錢包,錢包裡面會裝零錢、鈔票、信用卡等 list可以塞很多資料進去,不管是整數(integer)或是字串(string)都可以! ---- ```python a = ['one', 1, 'two', 2.0] # 列表(list) print(a[0]) print(a[1]) print(a) ``` 特別要注意的是,從上面的例子可以看出,電腦儲存的資料是從 **0** 開始數的! ---- - dictionary(字典) ![](https://i.imgur.com/1AHRK1O.jpg) dictionary就像是一個上鎖的箱子,要拿到裡面的東西,我們必須要有那把獨特的鑰匙 dictionary的每個元素分成兩個部分,前面是`key`,中間隔一個冒號,後面是`value`。特別注意的是,`key`的資料型態一定是==string(字串)== 而dictionary的特色就是,我們要用獨一無二的`key`去找出與其相對應的`value`。例子就參考下面的範例code。 ---- ```python pudding = { 'price': 50, 'flavor': 'milk' } #字典(dictionary) print(pudding) print(pudding['price']) ``` ---- - `==` 用來比較是否相等 ```python print(1 == 1) x = 3 y = 4 print(x == y) ``` 從上面的例子可以知道,`==`基本上就是你們平常在算數學時會看到的`=` 只是在程式語言的世界中 : `=` 表示賦值 `==` 用來判斷左右兩邊的東西一不一樣,如果一樣的話這個敘述就是True,不一樣就是False ---- - `!=` 用來比較是否不相等。剛好跟上面介紹的`==`概念是相反的。 ```python x = 3 y = 4 print(x != y) ``` ---- - 條件(if, else) 當我們想在某些條件成立的狀況下才做事,意即這個條件是`True`時我們才做事 這個概念就像英文的文法一樣(if....) ```python i_am_handsome = True # i_am_handsome = False if i_am_handsome: print("Thank You<3") else: print("So Sad:(") icecream = 3 if icecream == 3: print("I love icecream!") print("Icecream is so yummmmy!") ``` 大家應該也有注意到,在`if`跟`else`的下一行都會縮排。這個縮排的意義是要告訴電腦,接在`if`後面有哪幾行是這個`if`的範圍。亦即標示清楚如果進到這個`if`裡面,有哪幾行的程式碼是我們必須要執行的。例如icecream的那個例子,那兩個print就是進入`if`後必須執行的。 ---- - 條件(if, else) 其實除了上述的寫法,還有另外一種是用在`string(字串)`上的 當我們要檢查某個特殊的字串是否出現在其他字串中,我們可以這樣寫 ```python if "a" in "aabbccc": print("Ya!") ``` 其中,`a`就是我們想要找的string。而我們想要判斷`a`有沒有出現在`aabbccc`這個string裡面,如果有的話,這個敘述就是一個對的(True)敘述。 同樣的,我們當然可以把這些string存在一個變數裡面,然後再拿這些變數去做判斷。 ```python zoo = "Tiger_Elephant_Monkey" my_favorite_animal = "Monkey" if my_favorite_animal in zoo: print("Hurray!") ``` ---- - 迴圈 當我們想做一件事情很多次,例如我們想買很多隻冰淇淋 ![](https://i.imgur.com/ANoT3CY.jpg) ---- 你以為這樣就沒了嗎? 不,故事還沒說完 ---- ![](https://i.imgur.com/VtpQbt9.jpg) ---- `icecreams`在這邊是一個list,裡面有編號1~5的冰淇淋。而我們另外立了一個`icecream`,它會從 1 開始被裝進各個編號的冰淇淋,然後去做for迴圈裡所寫的事情。 ```python icecreams = [1, 2, 3, 4, 5] for icecream in icecreams: print(icecream) ``` ---- - 函式 ![](https://i.imgur.com/n9TpBtz.jpg) 函式就像裁縫店一樣,它會產出款式相同的衣服,但是會為了每個人不同的身材量身打造。 舉`len()`這個函式來說,顧名思義它就是負責找出長度(length)的函式。但不同的東西長度當然不同,因此它會接收**參數**,最後才回傳結果。 ```python my_string = "hello" x = len(my_string) #把len()所回傳的結果存在x裡面 print(x) ``` 最後要提醒的就是,不論參數給什麼,函式本身所做的事是不會變的,如上面提到的例子,`len()`的功能就是找出我們所丟給它的東西的長度。 ---- - 引入函式庫 Python 有很多的函式庫 ![](https://i.imgur.com/84W16kJ.jpg) 剛剛我們把函式看作裁縫店,現在我們將函式看成一項工具。因為函式接受到的參數雖然不盡相同,但函式所要做的事情是一樣的。 而既然函式是一項工具,那麼函式庫便是裝有各式各樣工具的工具箱了! ---- 想要安裝或是引入他們的時候怎麼辦? ![](https://i.imgur.com/Hkhu6ZW.png) ---- 例如 math 函式庫裡面有 sqrt(開根號), gcd(最大公因數) 等等 那我們可以 ```python import math print(math.sqrt(520)) ``` ---- 如果你連 math. 都懶得打 你也可以 ```python from math import gcd print(gcd(520, 1314)) ``` --- ## Learn Python Scrapping ---- ### Lesson 1 全部連結! ![](https://is1-ssl.mzstatic.com/image/thumb/Purple115/v4/44/46/b2/4446b20a-45a9-b174-7713-5879152c5f8e/AppIcon-1x_U007emarketing-85-220-0-4.png/246x0w.jpg) ---- 進入 http://iogames.fun ![](https://i.imgur.com/Z7rRBFO.jpg) ---- 目標: 列出所有的遊戲連結 ---- 首先 將我們要用的函式庫引入 ```python= import requests from bs4 import BeautifulSoup import lxml ``` ---- 再來 我們用 **requests** 的 **get** 函數 來 get 到我們想要的網址 並存到 res 這個變數裡面 然後我們把 **res.text** 放到 **BeautifulSoup** 這個函數裡面 並加上**字串型態**的 **lxml** 這個參數 最後可以 print(soup.prettify()) 來檢查結果是否正確的拿到想要的 html ```python import requests from bs4 import BeautifulSoup import lxml url = "http://iogames.fun" res = requests.get(url) soup = BeautifulSoup(res.text, "lxml") print(soup.prettify()) ``` ---- 這時候會看到這個網頁的 html 在你看來可能是一堆亂碼 不過其實他是有意義的 檢查正確之後就可以把 print **註解**掉 => ```python # print(soup.prettify()) ``` ---- 那你接下來一定會有個疑問 html code 這麼大一串 啊我怎麼知道要去哪一段找我要的東東啊QQ ![](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS4sqWXUJLipAIKpqFJbXOk_lrwo4aLkZKIV9Y_MPc3ieCPJECB) ---- **F12 是你的好朋友** 打開你的網頁 ![](https://i.imgur.com/XBs1mYO.jpg) ---- 按下F12,點選**左上角**「**選取頁面中的元素**」 ![](https://i.imgur.com/O51ppml.png) ---- 選取包含遊戲的區塊 ![](https://i.imgur.com/j0Vc38V.png) ---- 可以看到我們這裡選的是 ```htmlmixed <div class='tiles'> </div> ``` 這個**標籤**的名稱就叫作 **div** 然後他有一個**屬性(attribute)** 叫做class ---- 這裡我們稍微帶過 html 的標籤與屬性 標籤就像是人們會有不同的職業,而不同的職業會有不同的工作內容 因此網頁中不同的區塊會有不同的標籤,就表示他們有著不同的特性 (可以參考下面所列一些常見的標籤) ![](https://i.imgur.com/ykqcuIX.jpg) ---- - **\<a\>**: anchor 連結,通常會有 **href** 這個屬性,代表要連向的網址 - **\<div\>**: division 區塊,html 中重要的標籤,區分一個個不同的區塊,通常也是爬蟲的時候優先找的對象 - **\<img\>**: image 顧名思義,通常有 **src** 這個屬性,代表圖片的網址 - **\<h1\>**: 有 **h1** ~ **h6**,代表不一樣大小的標題 ---- 還有一些重要的屬性 ![](https://i.imgur.com/ugSjbcN.jpg) ---- - **class**: 代表一種類別,通常同一種**class**的標籤會有同樣的性質,所以也是爬蟲中重要的屬性 - **id**: 像你的身份證號碼,通常一個**id**只屬於一個標籤 ---- 我們發現所有的遊戲連結都會在一個 **a** 標籤裡面 而這些 **a** 標籤共同的特性是我們想要關注的 我們發現他們都長這樣 ```htmlmixed <a class="tiles_item" href="..."></a> ``` 前面有提過連結都會在 a 標籤的 **href** 裡面 所以我們先想辦法把 a 標籤拿到手 ---- 這裡介紹 BeautifulSoup 的第一個函數 **find_all** ```python # find_all("標籤名稱", 屬性=...) ``` 回傳一個 **list** 包含符合的標籤們 ---- 我們接序前面的 Code ```python import requests from bs4 import BeautifulSoup import lxml url = "http://iogames.fun" res = requests.get(url) soup = BeautifulSoup(res.text, "lxml") links = soup.find_all("a", class_="tiles-item") for link in links: print(link["href"]) ``` ---- 好像成功的印出東西了 不過似乎不太像是一個正常的網址 所以我們加上伺服器的名字 ```python for link in links: # (x) print(link["href"]) print(url + link["href"]) ``` --- ### Practice 1 瘋狂購物 ---- **目標:** 在蝦皮拍賣中搜尋任意物品 整理出搜尋到的物品的連結、名稱、價格 ![](https://i.imgur.com/ViVnp10.png) ---- 連結: [https://shopee.tw](https://shopee.tw) --- ### Lesson 2 可愛貓貓 ![image alt](https://www.seekpng.com/png/detail/22-224564_pusheen-computer-png-svg-stock-pusheen-the-cat.png) ---- **目標:** 貓貓很可愛 所以我們要蒐集貓貓的圖片 dcard 有個寵物版 我們就從裡面找出貓貓的圖片並且下載下來吧 ---- 連結: [https://www.dcard.tw/f/pet](https://www.dcard.tw/f/pet) ---- 那我們跟第一課一樣 先觀察 dcard 寵物版的網頁 ![](https://i.imgur.com/JAdZddq.png) ---- 我們會想要把每個文章的連結擷取出來 再分別從中找出裡面的圖片 並利用 python 來下載他們 跟 lesson 1 一樣 我們要的超連結都會在 **a** 標籤的 **href** 所以我們要想辦法收集這些標籤 ---- 跟 lesson 1 一樣 我們按下 F12 點選左上角的「選取頁面中的元素」按鈕 ![](https://i.imgur.com/YuqKjY9.png) 移動鼠標到文章的連結 可以看到我們選到一個 a 標籤 ```htmlmixed <a class="PostEntry_root_V6g0rd">...</a> ``` ---- 首先 一樣先引入需要的函式庫 ```python import requests from bs4 import BeautifulSoup import lxml ``` ---- 再來跟 lesson 1 一樣 我們用 **requests** 的 **get** 函數以及 **BeautifulSoup** 來解析 ```python import requests from bs4 import BeautifulSoup import lxml res = requests.get("https://www.dcard.tw/f/pet") soup = BeautifulSoup(res.text, "lxml") ``` ---- 再來我們利用 BeautifulSoup 的 **find_all** 函數 找到所有文章的 **a** 標籤 ```python for a in soup.find_all("a", class_="PostEntry_root_V6g0rd"): print(a["href"]) ``` ---- 我們拿到一個個文章的標籤之後 我們就利用可以利用 **a** 標籤的 **href** 屬性 加上 **requests** 的 **get** 函數跟 **BeautifulSoup** 函數 請求一個個單獨的文章了 ```python for a in soup.find_all("a", class_="PostEntry_root_V6g0rd"): res = requests.get("https://www.dcard.tw" + a["href"]) soup = BeautifulSoup(res.text, "lxml") ``` ---- 做到這步其實可以開始抓出圖片了 不過我們要先來判斷這篇文章跟貓貓有沒有關係 那我們要如何判斷呢 ![](https://i.imgur.com/kwJkFXL.png) ---- 我們可以看到每個文章的底下都有一些tag 所以我們只要抓出所有的 tag 判斷有沒有「貓」在裡面 我們先用**選取頁面中的元素** 看一下 tag 的 html 標籤是什麼 我們可以發現是 ```htmlmixed <a class="TopicList_topic_1XGOjs">...</a> ``` ---- 所以我們可以再用一次 find_all 函數 然後判斷**貓**這個字有沒有出現在 a 的文字裡面 ```python for a in soup.find_all("a", class_="PostEntry_root_V6g0rd"): res = requests.get("https://www.dcard.tw" + a["href"]) soup = BeautifulSoup(res.text, "lxml") tags = soup.find_all("a", class_="TopicList_topic_1XGOjs") if "貓" in [tag.text for tag in tags]: print(tags) ``` ---- 找出所有跟貓有關的文章之後 我們可以再用一次 F12 的**選取頁面中的元素**找出內文的部份 ![](https://i.imgur.com/iLbG66r.png) 可以看到我們選到 ```htmlmixed <article class="Post_root_23_VRn"></article> ``` ---- 那這裡我們要學一個新的 BeautifulSoup 函數 跟 find_all 很像的 find 前面我們說過 find_all 會回傳一個 list 因為可能會找到很多個 而 find 只會找到一個而已 所以會回傳單一的元素 而其他的用法基本上跟 find_all 一樣 ```python if "貓" in [tag.text for tag in tags]: article = soup.find("article", class_="Post_root_23_VRn") ``` ---- 最後 找到所有內文的圖片 ![](https://i.imgur.com/XwrIf74.png) ---- 我們可以看到是 img 的標籤 而圖片的網址我們可以看到是存在 src 這個屬性裡面 ```python if "貓" in [tag.text for tag in tags]: article = soup.find("article", class_="Post_root_23_VRn") for img in article.find_all("img"): print(img["src"]) ``` ---- 圖片怎麼下載的部分 我們提供了一個函數來幫助你 所以我們可以直接利用 downloader 函數 ```python from download import downloader if "貓" in [tag.text for tag in tags]: article = soup.find("article", class_="Post_root_23_VRn") for img in article.find_all("img"): downloader(img["src"]) ``` ---- 大功告成 --- Downloader 的介紹(參考用) ---- downloader 的一個參數 src src 指的是要下載的地址 ```python= # download.py import random import string import requests def downloader(src): print(f"Downloading {src} ...") with requests.get(src, stream=True) as res: res.raise_for_status() fn = "./images/" + ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) + ".jpg" with open(fn, "wb") as f: for chunk in res.iter_content(chunk_size=8192): if chunk: f.write(chunk) print("Done.") ``` 我們把這段 code 慢慢分開來講解 ---- ```python requests.get(src, stream=True) ``` 讓讀進來的資料不會一次就被寫入 當你的 requests 可能記憶體會很大(ex. 影片、圖片) 你就應該用 stream=True ---- ```python res.raise_for_status() ``` 當錯誤發生的時候 等待下一個正確的回應 ---- ```python res.iter_content(chunk_size=8192) ``` 跑過所有 request get 那到的 response(回應資料) ---- ```python with open(fn, "wb") as f: f.write(chunk) ``` python 寫入檔案的方式 要先 open 一個 file 檔案的位置名稱就是 dst 並且告訴他你打開這個檔案的作用是要做什麼 我們是要寫入一個二進位檔案 所以用 wb ---- chunk https://www.byvoid.com/zht/blog/http-keep-alive-header ---- 隨機的字串產生 ```python import random import string random_string = ''.join(random.choices(string.ascii_uppercase + string.digits, k=size)) ``` ---- str.join(array) 就是把陣列裡的字串都連接再一起 並且中間用 str 來分隔 ```python print(",".join(["a", "b", "c", "d"])) # a,b,c,d ``` random.choices(字串集合, 長度) 就是從字串集合裡面做出某長度的隨機字串 string.acsii_uppercase 大寫字母 string.digits 數字 --- ### Extra Practice ****** **hard** ****** ---- 眼尖的同學們應該會發現 其實我們並沒有把整個 dcard pet 的圖片全部抓下來 我們抓的只有一部份的文章而已 這是因為當你做 ```python requests.get("https://www.dcard.tw/f/pet") ``` 的時候 dcard 只會回傳部分的文章 但是當你將頁面往下滾 就會有更多文章載入了 ---- 目標: 爬到更多的文章 可以設定爬到 100 篇就停下來
{"metaMigratedAt":"2023-06-14T21:11:55.219Z","metaMigratedFrom":"Content","title":"Python 爬蟲(詳細版)","breaks":true,"contributors":"[{\"id\":\"c94b5949-af35-40c0-b13c-36bc311fc68c\",\"add\":17600,\"del\":8587},{\"id\":\"596938fa-db11-427a-9336-7b937648a7a4\",\"add\":9851,\"del\":7731},{\"id\":\"3adbc7c8-0f40-4e05-b033-9162a6e709b1\",\"add\":725,\"del\":240}]"}
    1216 views