Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Python_Crawling_Tutorial

afun
November 17, 2018

 Python_Crawling_Tutorial

afun

November 17, 2018
Tweet

More Decks by afun

Other Decks in Programming

Transcript

  1. • Research Assistant @ Sinica • Python, R • Research

    ◦ Deep Learning ◦ Computer Vision
  2. 爬蟲目的 • 做深度學習的時候,缺 training data … • 做文字探勘的時候,缺文本 data …

    • 做輿情分析的時候,缺輿論 data … • 做第三方平台要比價的時候,缺即時價格資訊 … 在做各種實驗或是分析的時候,總是會遇到缺少資料的時候 不論是否要即時性,或是要大量資料,都有爬蟲的需求
  3. 遊走在法律邊界的爬蟲 • GDPR (EU General Data Protection Regulation) • CFAA

    (Computer Fraud and Abuse Act) • 非法入侵動產 (Trespass to chattels) • 重大新聞不當挪用 (misappropriation of hot new) • 隱私權 • 網頁內容著作權規屬 • 網頁使用者協議 • …
  4. 爬蟲目的 - 暗黑板本 • 為了提升網路評價,分身帳號刷點擊跟評論... • 為了提升品牌曝光,搜尋引擎爬蟲策略更改... • 為了連假回家車票,需要爬蟲幫忙搶票... •

    做第三方平台要比價的時候,缺即時價格資訊 … (疑?說過了?) 基本上人類在網路上的行為,都可以用爬蟲取代 只有該行為有價值,就會有人想要用程式大量且快速的摹擬人類操作 點擊率與數據在現代社會可以直接帶來現金流
  5. 爬蟲戰爭 • 使用者透過爬蟲抓資料 • 企業透過反爬蟲策略擋下爬蟲 • 無辜使用者被反爬蟲誤傷 • 失控的爬蟲瘋狂送請求 •

    沒做好的防護措施的企業被癱瘓 • ... 我是實際請求行為 我才是實際請求行為 我們是實際請求行為 我們也是實際請求行為
  6. Python 爬蟲 很多程式語言都可以撰寫爬蟲程式,但是因為 Python 學習門檻較低 所以這份 Tutorial 會以 Python 當作主要使用的程式語言

    (Python 的中文是大蟒蛇,爬蟲類...,跟爬蟲程式沒有關係!) 雖然 Python 在爬蟲這邊需要用到的概念不難, 但還是建議先熟悉 Python 基本語法之後在來參考這份 Tutorial
  7. 基本爬蟲流程 1. 取得網站的 HTML ◦ 透過 requests 送出請求取得 HTML 2.

    解析資料以取得目標資訊 ◦ 透過瀏覽器的開發者工具,觀察目標資訊位置 ◦ 透過 BeautifulSoup 解析 HTML 3. 重複以上過程
  8. 元素組成 我們說 HTML 相當於網頁的骨幹,代表網頁的組成架構 而這就是由 DOM 樹決定架構,加上元素的標籤來決定段落用途 元素的組成包含了 • 標籤:通常成對出現,說明元素定義

    • 屬性:標籤可以有多種屬性,說明元素性質 • 內容:通常是顯示的文字,說明元素的值 <h1 id="title"> Header </h1> 還會有各自屬性的值, 有可能代表行為或是外觀
  9. 開始寫爬蟲之前 • Github ◦ 許多爬蟲程式在 Github 上都有,有時候可以不用全部都重頭開始自己寫 ◦ e.g. PTT

    Crawler • API ◦ 許多公司提供 API 讓使用者可以在遵循公司規定的情況下拿到整理後的資料 ◦ e.g. Facebook Graph API,Google Places API • 道德規範 (網頁使用者協議) ◦ 爬蟲是一個不斷送請求的過程,而頻繁大量的請求會對網站伺服器造成負擔 ◦ 雖然非強制性,但大家練習的時候請遵守規範 ◦ robots.txt 規範通常會放在網站的根目錄 (e.g. https://www.facebook.com/robots.txt ) ◦ 詳細的規範可以參考 wiki 與 google 文件
  10. 請求種類 我們送請求到對方伺服器其實就像我們寄信到對方家裡, 寄信有很多種方式,請求也有許多種類 (參考 MDN) 其中我們最常用的是 GET 與 POST 這兩種

    • GET:請求內容包含在 header,類似寄明信片的方式 • POST:請求內容包含在 body,類似寄平信的方式 body header 參考內容:淺談 HTTP Method
  11. POST 請求 POST 允許在 body 裡放資料,就像是放在信封裡的信件 比起 GET 相對安全,可以傳送的資料也更多 原網址:https://www.mywebsite.com/

    請求後網址:https://www.mywebsite.com/ 網址不會改變 補充說明:GET 與 POST 底層都是以 TCP 實作,所以其實這兩者基本上差不多,只 是透過不同的標籤 (HTTP method) 決定實作細節
  12. Status code • 1xx 訊息 - 暫時跟你說一下現在的進度 • 2xx 成功

    - 我們了解也接受你要做的事情了 • 3xx 重新導向 - 這邊還有一些額外的程序要做 • 4xx 請求錯誤 - 我們無法接受你的錯誤 • 5xx 伺服器錯誤 - 抱歉,我們內部出了一點狀況 請求查看 html 200 參考內容:Wiki - HTTP status code 1. 收到請求 2. 理解請求內容 3. 接受請求
  13. 透過比較高階的套件 requests, 可以很簡單的實作 HTTP method (GET/ POST) import requests url

    = 'http://research.sinica.edu.tw/' response = requests.get(url) # GET 請求 response.encoding = 'utf-8' # 解決中文問題 print(response.text) # HTML 架構 程式 - 發送 GET 請求
  14. 程式 - 發送 GET 請求 如果成功收到回應,透過回應的 text 可以取得 HTML 檔案的字串

    原始檔案很亂,需要透過解析器 (parser) 幫我們找到有用的資訊
  15. 程式 - 解析網頁 BeautifulSoup 背後的解析器有多種選擇 • html.parser (default) • lxml

    (速度快,高容錯) • html5lib (速度慢,與瀏覽器相同的 parse 方法) from bs4 import BeautifulSoup # 假設已經送出請求拿到回應 resp soup = BeautifulSoup(resp.text, 'lxml')
  16. 程式 - 解析網頁 當我們要找尋目標元素時,通常會根據標籤或是屬性來定位元素 (官方文件) soup.p # 尋找網頁中第一個 p tag

    soup.find('p') # 尋找網頁中第一個 p tag soup.find_all('p') # 尋找網頁中所有 p tag # 尋找網頁中所有 id 是 main 的 p tag soup.find_all('p', {'id': 'main'}) soup.p.img # 尋找網頁中第一個 p tag 底下的 img tag soup.p['style'] # 取得 p tag 中 style 屬性的值 soup.p.text # 取得 p tag 中的文字
  17. 程式 - 發送 POST 請求 查找方式可以透過開發者工具的 Inspect 功能找出元素在 HTML 內的位置

    然後透過觀察就會發現疑似亂碼的片段其實在元素的屬性裏面就有
  18. 程式 - 發送 POST 請求 與 GET 請求的過程大同小異,只要記得在送出請求的時候附上相關資訊 最後在把回傳的 HTML

    傳入 parser 定位找出查詢結果的資訊 import requests url = 'https://www.thsrc.com.tw/tw/TimeTable/SearchResult' form_data = { 'StartStation': '...', # 填上相關站別的值 'EndStation': '...', # 填上相關站別的值 … # 填上其他資訊 } response = requests.post(url, data=form_data) # POST 請求
  19. BeautifulSoup - CSS Selector BeautifulSoup 同時支援 CSS selector,可透過 .select 的方式鎖定目標

    soup.select('.test') # 尋找所有 css class 為 test 的 tag soup.select('#test') # 尋找所有 id 為 test 的 tag soup.select('div, p') # 尋找所有的 div 與 p tag soup.select('div p') # 尋找所有 div 中的 p tag soup.select('div > p') # 尋找所有 parent 是 div 的 p tag soup.select('a[href^="url"]') # 尋找所有連結開頭是 url 的 a tag 參考內容:CSS Selector Reference,CSS Selector Tester
  20. 練習 https://www.google.com.tw/search 觀察 Google Search 的網址 得知 API 後加上 「q=關鍵字」

    就可以得到搜尋結果頁面 • 練習透過 BeautifulSoup CSS Selector • 取得第一個頁面上所有結果的標 題與連結網址
  21. 程式 - 下載圖片 下載圖片有很多種方式,我這邊以 requests 套件當作範例 # 假設我們已經有圖片網址 image_url resp

    = requests.get(image_url, stream=True) with open('logo.png', 'wb') as f: # receive 10240 bytes per chunk for chunk in resp.iter_content(chunk_size=10240): f.write(chunk) 程式執行結束後,就會把圖片下載下來並存成 logo.png
  22. 程式 - 圖片格式資訊 為了要用正確的副檔名存檔,我們必須下載下來之後先判斷圖片格式 這邊可以藉由 PIL.Image 來判斷格式 from PIL import

    Image resp = requests.get(image_url, stream=True) image = Image.open(resp.raw) print(image.format) # e.g. JPEG # 假設我們重新組合圖片檔名與副檔名 logo.jpeg 之後 image.save('logo.jpeg') # 儲存圖片
  23. 定位策略 我們知道 BeautifulSoup 可以在 tag 裏面再次搜尋, 一般的策略都是透過定位目標 tag 的上一層,藉此縮小搜尋範圍再搜尋 可是還是會有跟目標

    tag 同一層同時存在其他不需要的超連結 這種情況通常就需要額外花時間去解析結構 檔案敘述 1 檔案敘述 2 檔案敘述 3 檔案敘述 4 檔案敘述 5 e.g. 目標僅有 PDF 檔案
  24. 程式 - 定位策略 由於 HTML5 的架構類似家族樹的概念,<a> tag 可以視作 <img> tag

    的 parent 因此除了由外而內縮小範圍尋找,我們也可以透過定位 PDF icon 由內往外找 images = soup.find_all('img', { 'src': re.compile('application-pdf.png') } for image in images: # 透過 parent method 尋訪 img tag 的上一層 tag print(image.parent['href'])
  25. 檔案的絕對路徑與相對路徑 前面提到圖片的時候我們知道圖片路徑在 <img> tag 的 src 屬性 而現在我們知道超連結檔案的檔案路徑在 <a> tag

    的 href 屬性 上述兩者皆代表了檔案位置,同樣都有以下這兩種表達方式 • 絕對路徑,e.g. http://exam.lib.ntu.edu.tw/graduate • 相對路徑,e.g. /exam/sites/all/themes/ntu/logo.png
  26. 程式- 轉換路徑 雖然我們也可以透過字串處理組合出絕對路徑, 但是 URL 路徑有其意義,建議透過 urllib.parse.urljoin 組合 from urllib.parse

    import urljoin # 假設已經取得絕對路徑 abs_path 與相對路徑 rel_path print(urljoin(abs_path, rel_path))
  27. 程式 - 偽裝身份送出請求 為了反反爬蟲,我們需要額外在送出請求時加上身份識別, 透過 requests 的實作如下 headers = {'User-Agent':

    my_user_agent} resp = requests.get(url, headers=headers) 另外,Python 也有 fake-useragent 可以隨機產生各種身份 其實並不用每次都去觀察瀏覽器實際 User Agent
  28. 遍歷網站 - 概念 (1) 第一個非常直覺的想法是透過 loop 將所有網址的超連結都送出 request history root

    index2 index3 從 root 開始搜尋 超連結 搜尋到新網頁 搜尋到新網頁
  29. 遍歷網站 - 問題 (1) 但這樣無法發現其他網頁的超連結 history root index2 index3 從

    root 開始搜尋 超連結 搜尋到新網頁 搜尋到新網頁 hidden 僅能從 index3 連結
  30. 練習 (進階) Website: Test-Crawling-Website/portfolio 請觀察並透過爬蟲程式下載 • 下載 2018/01/29 14:39:10 之後

    修改過的圖片 • 附上 User-Agent • 請依正確格式儲存圖片 備註:要了解 Last-Modified 與 ETag 之間的差異
  31. 合法超連結 參考 w3schools 的文件 href value 敘述 範例 absolute URL

    絕對路徑 https://gushi.tw relative URL 相對路徑 /ex1/index1.html anchor 其他 tag #top other protocols 其他協定 mailto://[email protected] JavaScript 程式碼 javascript:console.log("Hello")
  32. 合法超連結 - Other Protocol Protocol 稱為協定,在網路上各種不同的傳輸都有不同的協定 一般熟知的 http 與 https

    就是其中一種,而其他協定也有可以是超連結目標 • 信箱 mailto:// • 文件 ftp:// • …
  33. 合法超連結 - JavaScript JavaScript 是現代網站很常使用的程式語言之一, 雖然不常在網頁中看到這種寫法,但有可能會是反爬蟲手段之一 將 <a> tag 透過

    CSS 隱藏,然後在 href 屬性呼叫 JS function, 因為一般使用者看不到不會去點擊, 但如果透過程式對該超連結送出請求,即可判定為爬蟲程式將之拒絕
  34. 程式 - 過濾超連結 我們這邊可以很簡單的透過 regular expression 跟 urlparse 來過濾 練習的話可以參考線上工具做測試

    # anchor check1 = re.match('.*#.*', href) # protocol check2 = re.match('[^http|https].*', urlparse(href).scheme) # JS check3 = re.match('javascript.*', href)
  35. 小結 - 路徑處理 關於路徑處理的部份,到目前為止我們知道要考慮 • 相對路徑轉為絕對路徑 • 過濾 anchor,others protocol,JavaScript

    經過上面這些處理之後,我們確認我們即將要送的是合法的網址 但是實際上在爬蟲時,還有其他方面的因素需要考慮
  36. 動態網站爬蟲 動態網站的問題是程式還沒有更改網站結構就回傳 HTML, 非常直覺的概念就是請網站程式更改完畢再回傳 HTML How ? • Selenium •

    Headless Chrome • Splash • … 現在很多服務都可以提供程式 render 後的 HTML,但這邊以 Selenium 介紹為主
  37. Selenium Selenium 其實是網頁自動化測試工具,所以他可以透過程式操作瀏覽器 也因此可以透過瀏覽器取得 render 後的 HTML • 透過 webdriver

    控制各大瀏覽器 • 因為是模擬瀏覽器操作,所以會比靜態網站爬蟲還要慢 # 假設要透過 Chrome webdriver 操作 Chrome from selenium import webdriver driver = webdriver.Chrome(path_to_webdriver) driver.get(url)
  38. webdriver webdriver 其實是各大瀏覽器自己出的,請注意 Python 操作的 webdriver 版本與系 統上安裝的瀏覽器版本相容 • Chrome

    webdriver • FireFox webdriver • … 另外要注意的是,控制不同的瀏覽器,selenium 的方法會有些許差異 這份教材會以 Chrome 為範例
  39. Selenium 定位 Tag 我們透過 Selenium 拿到 render 好的 HTML 後有兩種選擇

    • 跟之前一樣把 HTML 傳入 Beautifulsoup 定位 • 直接用 Selenium 定位 這邊會介紹 Selenium 的定位方式提供參考, 因為其中有 Beautifulsoup 沒有的定位方式
  40. Selenium 定位 Tag Selenium 的定位方式跟 Beautifulsoup 大同小異, 基本上只是 function name

    與回傳物件不太相同而已 • find_element_by_id • find_element_by_tag_name • find_element_by_class_name tag = driver.find_element_by_id('first') print(tag)
  41. XPath XPath 是一種類似路徑寫法的定位方式,Selenium 支援但 Beautifulsoup 沒有 語法 意義 / 從

    root 的地方開始選擇 // 從任意地方選擇 . 選擇當下這個 node .. 選擇當下這個 node 的 parent node @ 選擇 tag 屬性 * 選擇任何 node | OR
  42. XPath 與 Beautifulsoup 的定位 比較 XPath 的寫法與前面使用 Beautifulsoup 定位的差異 #

    Beautifulsoup soup.find_all('div')[2].find_all('div')[0] # Selenium XPath driver.get_elements_by_xpath( '/html/body/div[2]/div[0]' )
  43. 程式 - XPath 透過 Selenium 的 By 可以更簡單的更換定位方式 from selenium.webdriver.common.by

    import By # 尋找所有 p tag driver.find_elements(By.XPATH, '//p') # 尋找任意 id 為 first 的 tag driver.find_elements(By.XPATH, '//*[@id="first"]') # 尋找任意 id 為 second 或 third 的 h2 tag driver.find_elements(By.XPATH, '//h2[@id="second"] | //h2[@id="third"]' )
  44. 取得 XPath • 開發者工具 ◦ tag 按右鍵 > Copy >

    Copy XPath ◦ Chrome 與 Firefox 都支援 透過這種方式你會取得只針對該 tag 的 XPath 寫法 如果你是希望根據條件取得所有 tag 可以考慮透過這種方式取得之後再修改
  45. 程式 - Implicit 請求等待 Selenium 的最常使用的等待有 Implicit 與 Explicit 兩種

    Implicit Wait 通常適用於整個 Selenium 程式的預設等待, 當網頁元件暫時找不到時會進行等待,直到 timeout • 程式執行期間,每次執行尋找指令時都會進行 n 秒等待 • Implicit wait 在瀏覽器開啟期間都會運作 driver = webdriver.Chrome(path_to_webdriver) driver.implicity_wait(10) # 程式最多等 10 秒
  46. 程式 - Implicit 請求等待 driver = webdriver.Chrome(path_to_webdriver) driver.implicity_wait(10) # 最多等

    10 秒 最多等 10 秒就 要回傳 HTML 2 秒就好了,馬 上回傳 請求查看 首頁 請求查看 首頁 首頁 首頁
  47. 程式 - Implicit 請求等待 driver = webdriver.Chrome(path_to_webdriver) driver.implicity_wait(10) # 最多等

    10 秒 最多等 10 秒就 要回傳 HTML 10 秒到了,不 管怎樣先回傳 請求查看 首頁 請求查看 首頁 首頁 首頁
  48. 程式 - Explicit 請求等待 另外一種等待是更明確的 Explicit Wait,宣告一個等待物件之後給予條件 Selenium 本身提供很多種條件供調用,但如果有需求也可以自定義條件 from

    selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) el = wait.until(EC.presense_of_element_located( By.XPATH, '//div[@class="count" and text()]' ))
  49. 程式 - Implicit 請求等待 Explicit wait 主要針對特殊元件,或是需要等待某種屬性的狀態等,在精確的等待與 判斷時較適用 e.g. 是否可以被點擊,是否可見

    符合條件的 tag 好了 就回傳,最多等十秒 10 秒到了,不 管怎樣先回傳 請求查看 首頁 請求查看 首頁 首頁 首頁
  50. Implicit 與 Explicit 的比較 • Implicit 實作較簡單,而且只要宣告一次 • Implicit wait

    只作用在 find elements,無法檢查屬性 • Implicit wait 無法客製化,非預期的顯示時間可能會被忽略 • Implicit wait 沒有實際定義行為 • 推荐使用 Explicit wait,雖然實作上會比較複雜 • 不建議混用 Implicit 與 Explicit wait 參考來源:Implicit Wait 與 Explicit wait 的區別,Selenium with Python
  51. 練習 Website: http://24h.pchome.com.tw/region/D HBE 請觀察並透過爬蟲程式下載 • 檢查該網站是否為動態網站 • 使用 Selenium

    • 視窗最大化 • 設定 Implicit Wait • 透過 XPath 定位 • 取得商品的品項與價格資訊
  52. 練習 (進階) 簡易下載 reCAPTCHA v1 圖片 • https://www.google.com/recapt cha/demo/recaptcha •

    使用 Selenium • 使用 Implicit wait + time.sleep • 檢查圖片格式 • 下載五張圖片
  53. 身份驗證 Access Token 1. Facebook 中的 Access Token 可以代表用戶,粉絲專頁,或是應用程式的身份 2.

    我們在送出搜尋的請求時必須附上 Access Token 才會被視為是有效的請求 3. 根據身份的不同,能搜尋到的資料也不同 (權限管理) 一般來說這都會是短期 Token 但可以申請延長到 60 天 若是粉絲專頁甚至可以取得永久 token
  54. 從官方文件裏面可以得知 Graph 其實就是社交圖表的意涵, 我們可以把他想成社交網路的圖,包含 • 節點 Nodes,e.g. 用戶 / 相片

    / 文件 / 回應等... • 邊緣 Edges,節點之間的關聯,e.g. 用戶的相片,文件的回應... • 欄位 Fields,節點的資訊,e.g. 用戶的生日,社團的名稱... 所謂 Graph API 的 Graph
  55. Graph API 版本 API 有多種版本使用,幾乎所有的 API 請求都會往 graph.facebook.com 傳遞,除了 上傳影片的請求以外

    • 每個版本至少在 2 年內都可以使用,並且不會修改 • 平台變更紀錄,版本詳細資訊
  56. HTTP GET 請求 我們只需要對 Graph API 發出 HTTP GET 請求就可以讀取節點跟關係連線,

    通常還要附上 Access Token 讓 Graph API 判斷權限,回傳相關結果 Graph API HTTP GET 所需資訊 • Graph API 版本 X.Y,e.g. 2.10 • 節點或邊緣編號 • 搜尋條件 graph.facebook.com/vX.Y/{id}?{query-request}
  57. 取得留言內容 - HTTP GET request 根據前面提到的 HTTP GET 方式,我們可以知道請求的格式與所需資訊 version

    = 'v2.11' id = '1213927345375910' query = 'fields=comments&access_token={}'.format(access_token) url = https://graph.facebook.com/{}/{}?{}.format( version, id, query )
  58. 取得留言內容 - HTTP GET request 當我們對 graph.facebook.com 發送合法請求時, 對方會把分頁結果以 json

    字串格式回傳 # method 1: 透過 response 物件的 json method 轉成 dict resp = requests.get(url) data = resp.json() # method 2: 透過 json module 把 json 字串轉成 dict import json data = json.loads(resp.text)
  59. 練習 https://www.facebook.com/DoctorK oWJ 透過 Graph API 做 Facebook 爬蟲 •

    抓取粉絲專頁所有文章 ◦ 按讚與分享數 ◦ 標題與內文 • 輸出成 CSV
  60. 練習 (進階) https://www.facebook.com/appledail y.tw/posts/10156769966527069 透過 Graph API 做 Facebook 爬蟲

    • 抓取文章所有留言 ◦ message ◦ attachement ◦ application • 輸出成 CSV
  61. 案例分享 - 關鍵字分析 • 透過 Graph API 將粉絲專頁的文章相關訊息爬取下來 ◦ 文章標題,內文,按讚數,分享數,留言

    內容 • 透過 R 語言做分析 ◦ jieba 斷字處理 ◦ 過濾資料 (stop words, 頻率, 權重) ◦ 關聯性分析 (correlation) ◦ 視覺化
  62. Age Verification 從年齡檢查的頁面網址可以觀察到他明確的要判斷是否超過 18 歲 https://www.ptt.cc/ask/over18?from=%2Fbbs%2FGossiping... 1. 動態爬蟲:摹擬 click 事件點擊「已年滿

    18 歲」的按鈕 2. 靜態爬蟲:透過開發者工具查看,附上甚麼額外資訊可以繞過頁面檢查 評估後發現 Age Verification 並不需要夾帶太複雜的資訊,這邊會以靜態爬蟲實作並 繞過檢查
  63. Age Verification • 開發者工具 > Network • 檢查 requests header

    可以發現這邊使用 Cookies 傳送用戶狀態 我們可以偽造並在 GET 附上 Cookies 繞過檢查 c = {'over18': '1'} resp = requests.get(URL, cookies=c)
  64. 練習 https://www.ptt.cc/bbs/Gossiping/M. 1537847530.A.E12.html 爬取 PTT 上單一文章 • 繞過年齡檢查 • 爬取文章

    ◦ 作者 id, 暱稱, ip ◦ 文章標題, 發佈時間, 內容 • 爬取留言 ◦ 推噓 ◦ 留言者 id, 留言內容, ip, 時間
  65. 同標題/ 同作者文章列表 透過 API 我們可以不用爬全部的文章再自己搜尋 這邊有兩種 approach 的方式可以取得 href 1.

    在列表中找到特定文章後, 直接取得 dropdown 中的 href 2. 透過觀察可以知道 API 規則, 只要知道文章標題就可以自己生成 Hint: 回覆的文章會以 Re: 開頭, Re: 這個並不包含在 query 條件中
  66. 觀察 PTT Search API 觀察 API pattern 的方式彈性更高所以這邊會進一步解釋 /bbs/Gossiping/search?q=thread%3A… 這邊其實是將

    thread:[新聞] 魏揚自嘲「學成不知歸哪國」引轟笑 法官 做 URL UTF-8 encoding 才會變成一堆以 % + 兩個十六進位表示的字串 這種方式主要是被設計來在參數中使用保留字元 參考內容:W3schools - URL encoding
  67. 觀察 PTT Search API 邊操作變觀察 URL 可以發現如果是要依照 user id 去搜尋文章

    (e.g. id = yoyodiy) /bbs/Gossiping/search?q=author%3Ayoyodiy… 如果文章列表有分頁 /bbs/Gossiping/search?page=2&q=author%3Ayoyodiy…
  68. 練習 爬取 PTT 上標題如下的所有文章 [新聞] 2噸水晶球沿街滾 撞壞5輛汽 機車和民宅 • URL

    encoding • 參考上一個練習題把所有列表中 的文章都抓下來 Hint: urllib.parse.urlencode
  69. PTT 看板文章列表 看板最新文章列表的網址通常是稱做 index.html 檔案 https://www.ptt.cc/bbs/Gossiping/index.html 如果不是最新的文章列表則是會 index + 一串數字

    可以視為是分頁的頁碼 https://www.ptt.cc/bbs/Gossiping/index39207.html 透過分頁後看到的最新文章列表也會有頁碼 https://www.ptt.cc/bbs/Gossiping/index39208.html 因伺服器實作的原因 同一個頁面會有不同的網址
  70. 進階練習 • 爬取特定時間範圍內的所有文章 ◦ 需要考慮時間邊界 • 爬取文章及圖片 ◦ 考慮所有圖片可能會遇到的問題 ◦

    PTT 表特版爬蟲 • 減少請求次數 ◦ 可透過 search 演算法減少請求次數, e.g. binary search • Boosting ◦ multiprocess, multithread, async 實作 • Framework ◦ Scrapy
  71. fake-useragent • https://pypi.org/project/fake-useragent/ • 隨機生成 useragent import requests from fake_useraent

    import UserAgent ua = UserAgent() resp = requests.get(URL, headers={'User-Agent': ua.random} 可以透過此套件簡單的在 header 加上 user-agent 繞過簡單檢查 user agent 的反爬蟲策略
  72. browsercookie • https://bitbucket.org/richardpenman/browsercookie • 將你本地端的瀏覽器 cookies 載入,並轉換成 cookiejar object import

    requests import browsercookie cookies = browsercookie.chrome() resp = requests.get(URL, cookies=cookies) 可以透過此套件可以簡單繞過一些透過 cookie 驗證身份的服務 直接爬取你在瀏覽器就看的到的資料