Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Python crawling tutorial

基本 Python 網路爬蟲教材,可搭配 github 題目練習
https://github.com/afunTW/Python-Crawling-Tutorial

如有發現錯誤,歡迎告知

  • Soyez le premier à commenter

Python crawling tutorial

  1. 1. Python Crawling Chen-Ming Yang (@afun)
  2. 2. ● Research Assistant @ Sinica ● Python, R ● Research ○ Data Crawling ○ Deep Learning
  3. 3. README 這份教材包含一些題目與練習網站,需要的話可以參考當解答 ● 題目解答: github/Python-Crawling-Tutorial ● 部份範例網站:github/Test-Crawling-Website
  4. 4. 爬蟲入門
  5. 5. 爬蟲與網頁 介紹
  6. 6. 網路爬蟲其實就是一個會自動幫你從網路擷取資訊的程式, 通常會稱呼為 crawler 或是 spider 爬蟲無所不在,一直都有各式各樣的爬蟲在網路上擷取資訊, 也因此在產生了很多非使用者的請求,e.g. 搜尋引擎 網路爬蟲
  7. 7. 爬蟲目的 ● 做深度學習的時候,缺 training data … ● 做文字探勘的時候,缺文本 data … ● 做輿情分析的時候,缺輿論 data … ● 做第三方平台要比價的時候,缺即時價格資訊 … 在做各種實驗或是分析的時候,總是會遇到缺少資料的時候 不論是否要即時性,或是要大量資料,都有爬蟲的需求
  8. 8. Python 爬蟲 很多程式語言都可以撰寫爬蟲程式,但是因為 Python 學習門檻較低 所以這份 Tutorial 會以 Python 當作主要使用的程式語言 (Python 的中文是大蟒蛇,爬蟲類...,跟爬蟲程式沒有關係!) 雖然 Python 在爬蟲這邊需要用到的概念不難, 但還是建議先熟悉 Python 基本語法之後在來參考這份 Tutorial
  9. 9. 基本爬蟲流程 1. 取得網站的 HTML ○ 透過 requests 送出請求取得 HTML 2. 解析資料以取得目標資訊 ○ 透過瀏覽器的開發者工具,觀察目標資訊位置 ○ 透過 BeautifulSoup 解析 HTML 3. 重複以上過程
  10. 10. 爬蟲眼中的網站
  11. 11. 網站的組成 網站三本柱 ● HTML (架構) ● CSS3 (外觀) ● JavaScript (行為)
  12. 12. HTML (HyterText Markup Language) HTML 又稱做超文件標記語言,是由一堆預定義好的元素組成階層式架構的文件 元素的組成包含了 ● 標籤 ● 屬性 ● 內容 <標籤 屬性> 內容 </標籤>
  13. 13. HTML 結構 前面提到 HTML 是元素組成階層式架構的文件, 而元素以這種方式組合的樹狀結構,我們又稱為 DOM (Document Object Model) html head body <meta charset="utf-8" /> <title>Page Title<title/> <h1 id="title">Header<h1/>
  14. 14. 元素組成 我們說 HTML 相當於網頁的骨幹,代表網頁的組成架構 而這就是由 DOM 樹決定架構,加上元素的標籤來決定段落用途 元素的組成包含了 ● 標籤:通常成對出現,說明元素定義 ● 屬性:標籤可以有多種屬性,說明元素性質 ● 內容:通常是顯示的文字,說明元素的值 <h1 id="title"> Header <h1/> 還會有各自屬性的值, 有可能代表行為或是外觀
  15. 15. HTML 基本結構介紹 其實這不屬於 HTML 元素,而是 告知瀏覽器 HTML 版本的指令 HTML 的 root,告知瀏覽器這是 HTML 文件
  16. 16. HTML 基本結構介紹 網頁結構主體 定義名稱,metadata,相關文件 連結的地方
  17. 17. HTML 基本結構介紹 網頁上實際樣子 網頁相關定義與設定 網頁主架構
  18. 18. GET 爬蟲
  19. 19. 開始寫爬蟲之前 ● 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 文件
  20. 20. 請求種類 我們送請求到對方伺服器其實就像我們寄信到對方家裡, 寄信有很多種方式,請求也有許多種類 (參考 MDN) 其中我們最常用的是 GET 與 POST 這兩種 ● GET:請求內容包含在 header,類似寄明信片的方式 ● POST:請求內容包含在 body,類似寄平信的方式 body header 參考內容:淺談 HTTP Method
  21. 21. GET 請求 GET 請求會把資料放在 header 傳送,就像寄明信片一樣 資料很容易被看見,所以其實會有安全性的問題 一般操作會使用 GET,但是帳號密碼等隱私性高的資料一般不會用這種方式實作 我們要傳送到對方伺服器的資料 原網址:https://www.mywebsite.com/ 請求後網址:https://www.mywebsite.com/form?name=afun
  22. 22. POST 請求 POST 允許在 body 裡放資料,就像是放在信封裡的信件 比起 GET 相對安全,可以傳送的資料也更多 原網址:https://www.mywebsite.com/ 請求後網址:https://www.mywebsite.com/ 網址不會改變 補充說明:GET 與 POST 底層都是以 TCP 實作,所以其實這兩者基本上差不多,只 是透過不同的標籤 (HTTP method) 決定實作細節
  23. 23. Status code 平常我們在寄信時也有各種情況,例如收不到信,信件遺失,成功寄送等... 在網路上送請求也會有各種情況發生,我們會用 HTTP 狀態碼來紀錄狀態 然後在回應 response 中附上狀態讓你了解請求是否成功 (參考 MDN) 常用的狀態碼 Status code ● 200 OK ● 403 拒絕存取 ● 404 資源不存在 請求查看 html 200
  24. 24. 透過比較高階的套件 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 請求
  25. 25. 程式 - 發送 GET 請求 如果成功收到回應,透過回應的 text 可以取得 HTML 檔案的字串 原始檔案很亂,需要透過解析器 (parser) 幫我們找到有用的資訊
  26. 26. 解析網頁 原始 HTML 很難透過單純字串處理取得我們要的資訊, 這時候可以透過 BeautifulSoup 當作解析器 (parser), 之後再根據元素的相關資訊來找會比較方便 parser 尋找所有 p tag
  27. 27. 程式 - 解析網頁 BeautifulSoup 背後的解析器有多種選擇,因為速度快跟容錯能力高的優點, 我們這邊選擇使用的是 lxml from bs4 import BeautifulSoup # 假設已經送出請求拿到回應 resp soup = BeautifulSoup(resp.text, 'lxml')
  28. 28. 程式 - 解析網頁 當我們要找尋目標元素時,通常會根據標籤或是屬性來定位元素 (官方文件) 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 中的文字
  29. 29. 練習 Website: http://www.taipeibo.com/yearly/ 請觀察並透過爬蟲程式下載 ● 當年度週末冠軍排行榜 ● requests + Beautifulsoup ● 透過 pandas 輸出成 csv
  30. 30. POST 爬蟲
  31. 31. 判斷請求 所有的爬蟲都是從觀察開始, 我們要知道該送出 GET 還是 POST 請求, 才能撰寫對應的程式碼以及對應的資料 觀察 HTTP method 可以透過開發者工具輔助 以 Chrome 為例,按 F12 或是滑鼠右鍵點選檢查
  32. 32. 判斷請求 重新整理網頁之後可以透過 Network 分頁觀察請求封包 (範例: 高鐵時刻表) 要觀察的請求
  33. 33. 判斷請求 重新整理網頁之後可以透過 Network 分頁觀察請求封包 (範例: 高鐵時刻表) 這邊敘述了封包相關資訊,其中 Request Method 說明了請求的方式 我們可以發現這是個 POST 請求
  34. 34. 觀察 POST 需要附上的參數 以高鐵時刻表查詢為例,我們選擇好相關條件之後, 按下「立刻查詢」會送出一個 POST 請求到伺服器 如果爬蟲要實作這個請求,就必須知道 POST 到底送了哪些查詢條件到伺服器
  35. 35. 觀察 POST 需要附上的參數 同樣透過開發者工具觀察請求細節 Form data 紀錄了請求所發送的欄位 如果要模擬該請求,就要附上相關資訊
  36. 36. 程式 - 發送 POST 請求 與 GET 請求的過程大同小異,只要記得在送出請求的時候附上相關資訊 以高鐵時刻表查詢為例,我們現在知道要附上的欄位資訊 但是像 StartStation 是一連串很像亂碼的片段, 我們必須去觀察 HTML 架構找出相關資訊的位置
  37. 37. 程式 - 發送 POST 請求 查找方式可以透過開發者工具的 Inspect 功能找出元素在 HTML 內的位置 然後透過觀察就會發現疑似亂碼的片段其實在元素的屬性裏面就有
  38. 38. 程式 - 發送 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 請求
  39. 39. 練習 Website: https://www.thsrc.com.tw/tw/TimeTa ble/SearchResult 請觀察並透過爬蟲程式下載 ● 一個禮拜後的高鐵時刻表 ● 台北到台南下午兩點的班次 ● requests + Beautifulsoup ● 透過 pandas 輸出成 csv
  40. 40. 非文字靜態網 站爬蟲
  41. 41. 圖片爬蟲
  42. 42. 觀察圖片 Tag 以故事網站為例,我們一樣先透過開發者工具來檢查目標圖片 Tag Tag 本身不包含圖片資訊,而是以圖片位置代替
  43. 43. 文字爬蟲流程 請求查看 html 請求查看 html
  44. 44. 文字爬蟲流程 Beautifulsoup Parse 定位目標 tag 透過 .text 取得文字資訊
  45. 45. 圖片爬蟲流程 請求查看 html 請求查看 html
  46. 46. 圖片爬蟲流程 Beautifulsoup Parse 定位目標 tag 透過 src 屬性取得圖片位置
  47. 47. 圖片爬蟲流程 請求查看 圖片 請求查看 圖片
  48. 48. 程式 - 下載圖片 下載圖片有很多種方式,我這邊以 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
  49. 49. 練習 Website: https://www.pexels.com/ 請觀察並透過爬蟲程式下載 ● 5 張桌布圖
  50. 50. 副檔名的重要性 1. 副檔名是讓電腦決定要用甚麼軟體開啟檔案的提示 2. 更改副檔名不等於轉檔 3. 副檔名錯誤就無法正確開啟檔案 logo.jpg logo.png 使用 jpg 格式 讀取圖片 更改副檔名 使用 png 格式 讀取圖片 實際檔案格式 還是 jpg
  51. 51. 網路圖床 除了網站本身的圖片以外,有些網站提供用戶貼圖床的網址供預覽 e.g. PTT 備註: i.imgur.com/q6fMyz9.jpg 是圖片 imgur.com/q6fMyz9 是網站
  52. 52. 眼見不為憑 http://i.imgur.com/q6fMyz9.jpg
  53. 53. 眼見不為憑 http://i.imgur.com/q6fMyz9.png
  54. 54. 眼見不為憑 http://i.imgur.com/q6fMyz9.gif
  55. 55. 程式 - 圖片格式資訊 為了要用正確的副檔名存檔,我們必須下載下來之後先判斷圖片格式 這邊可以藉由 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') # 儲存圖片
  56. 56. 練習 Website: Test-Crawling-Website/portfolio 請觀察並透過爬蟲程式下載 ● 5 張圖片 ● 請依正確格式儲存圖片
  57. 57. 檔案爬蟲
  58. 58. 超連結檔案 檔案在網站上呈現的方式除了單純的文字超連結以外,還有圖片超連結 檔案爬蟲跟圖片爬蟲的流程基本上一樣,只是目標 tag 變為 <a> tag e.g. 台大考古題下載 圖片超連結檔案
  59. 59. 超連結檔案 透過開發者工具查看可以得知這個結構是 <a> tag 裏面包著 <img> tag 而 <a> tag 是我們的目標主體,<img> tag 只是單純代表 PDF icon
  60. 60. 定位策略 我們知道 BeautifulSoup 可以在 tag 裏面再次搜尋, 一般的策略都是透過定位目標 tag 的上一層,藉此縮小搜尋範圍再搜尋 可是還是會有跟目標 tag 同一層同時存在其他不需要的超連結 這種情況通常就需要額外花時間去解析結構 檔案敘述 1 檔案敘述 2 檔案敘述 3 檔案敘述 4 檔案敘述 5 e.g. 目標僅有 PDF 檔案
  61. 61. 程式 - 定位策略 由於 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'])
  62. 62. 檔案的絕對路徑與相對路徑 前面提到圖片的時候我們知道圖片路徑在 <img> tag 的 src 屬性 而現在我們知道超連結檔案的檔案路徑在 <a> tag 的 href 屬性 上述兩者皆代表了檔案位置,同樣都有以下這兩種表達方式 ● 絕對路徑,e.g. http://exam.lib.ntu.edu.tw/graduate ● 相對路徑,e.g. /exam/sites/all/themes/ntu/logo.png
  63. 63. 檔案的絕對路徑與相對路徑 ● 無法直接對相對路徑送 request ext ext ext A.com B.com C.com 我在 /ext/file.pdf 哪個 /ext ???
  64. 64. 檔案的絕對路徑與相對路徑 硬要對相對路徑送請求的話,會看到類似下面的 error message url = '/exam/sites/all/modules/pubdlcnt/pubdlcnt.php?file=http://140.11 2.115.12/exam/sites/default/files/exam/graduate/106g/106_graduate _4.pdf&nid=5814' resp = requests.get(url)
  65. 65. 轉換路徑 - 相對路徑轉為絕對路徑 外部請求之所以不知道相對路徑的位置是因為不知道 root ● 相對路徑:我在門口進來右轉的地方 ● 絕對路徑:我在從中研院門口進來右轉的地方 以上面例子來說,「中研院」就是 root, 有了 root 這個本體就可以組合相對路徑為絕對路徑
  66. 66. 轉換路徑 - 相對路徑轉為絕對路徑 ● 相對路徑:/exam/sites/all/modules/pubdlcnt/pubdlcnt.php ● 絕對路徑:http://exam.lib.ntu.edu.tw/graduate 組合後的路徑 :http://exam.lib.ntu.edu.tw/exam/sites/all/modules/pubdlcnt/pubdlcnt.php
  67. 67. 程式- 轉換路徑 雖然我們也可以透過字串處理組合出絕對路徑, 但是 URL 路徑有其意義,建議透過 urllib.parse.urljoin 組合 from urllib.parse import urljoin # 假設已經取得絕對路徑 abs_path 與相對路徑 rel_path print(urljoin(abs_path, rel_path))
  68. 68. 解析 URL 透過原始碼可以得知 urljoin 內部是透過 urllib.parse.urlparse 實作 將 URL 拆解成數個有意義的片段再去組合 <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
  69. 69. 其他下載檔案方式 (圖片適用) 有些人會建議使用 urllib.request.urlretrieve 來下載檔案 因為使用方式很簡單,只要給網址跟要存的檔案名稱即可 urlretrieve(url, file_name) 但我這邊舉的範例都會是以 requests 為主,官網也是這麼建議
  70. 70. 基本反爬蟲 - 身份識別 User-Agent 當我們對網站送出請求時,其實會送出很多其他的資訊,包含身份識別 透過程式送的話,一般來說不會包含身份識別 沒有身份 拒絕回傳 請求查看 html 請求查看 html
  71. 71. 查詢自己的身份 ● User-Agent 是一段描述瀏覽器使用的系統,平台,版本等資訊的字串
  72. 72. 查詢自己的身份 ● User-Agent 是一段描述瀏覽器使用的系統,平台,版本等資訊的字串 重新整理網頁,觀察瀏覽器送出的請求
  73. 73. 查詢自己的身份 ● User-Agent 是一段描述瀏覽器使用的系統,平台,版本等資訊的字串
  74. 74. 程式 - 偽裝身份送出請求 為了反反爬蟲,我們需要額外在送出請求時加上身份識別, 透過 requests 的實作如下 headers = {'User-Agent': my_user_agent} resp = requests.get(url, headers=headers) 另外,Python 也有 fake-useragent 可以隨機產生各種身份 其實並不用每次都去觀察瀏覽器實際 User Agent
  75. 75. 練習 Website: http://exam.lib.ntu.edu.tw/graduate 請觀察並透過爬蟲程式下載 ● 附上 User-Agent ● 第一個頁面所有 pdf 考古題
  76. 76. 網站爬蟲
  77. 77. 網站結構 網站是一堆檔案以階層式的方式組合的集合 rootgushi.tw image js article github.com ntu.edu.tw index history index
  78. 78. 瀏覽網站行為 不管是圖片或是網頁都只是一份文件, 瀏覽網站其實只是透過網路呼叫遠端電腦的檔案 從首頁到「全部文章」的頁面
  79. 79. 瀏覽網站行為 rootgushi.tw image js article github.com ntu.edu.tw index history index 首頁 全部文章
  80. 80. 遍歷網站 - 從網頁到網頁 網頁之間的連結也是透過超連結, 透過開發者工具查看可以發現同樣是透過 <a> tag 首頁 首頁 請求查看 html 請求查看 html
  81. 81. 遍歷網站 - 從網頁到網頁 網頁之間的連結也是透過超連結, 透過開發者工具查看可以發現同樣是透過 <a> tag 首頁 parse BeautifulSoup 定位 「全部文章」檔 案路徑
  82. 82. 遍歷網站 - 從網頁到網頁 網頁之間的連結也是透過超連結, 透過開發者工具查看可以發現同樣是透過 <a> tag 全部文章 全部文章 請求查看 html 請求查看 html
  83. 83. 遍歷網站 - 概念 (1) 第一個非常直覺的想法是透過 loop 將所有網址的超連結都送出 request history root index2 index3 從 root 開始搜尋 超連結 搜尋到新網頁 搜尋到新網頁
  84. 84. 遍歷網站 - 問題 (1) 但這樣無法發現其他網頁的超連結 history root index2 index3 從 root 開始搜尋 超連結 搜尋到新網頁 搜尋到新網頁 hidden 僅能從 index3 連結
  85. 85. 遍歷網站 - 概念 (2) 為了解決遍歷其他網頁裡的超連結,有兩個很直覺的作法 ● recursive 遍歷 ● 建立清單紀錄待尋訪的網址,再對待尋訪清單的網址做 loop
  86. 86. 遍歷網站 - 問題 (2) 但是現代網站通常都會有「導覽列」的設計,而這很容易會造成無窮迴圈
  87. 87. 遍歷網站 - 概念 (3) 為了預防無窮迴圈的問題,通常還會紀錄我們尋訪過的網址 設想多一點的會連網頁上次更改的時間 Last Modified 或是 ETag 等資訊都紀錄 相關細節可以參考循序漸進理解 HTTP Cache 機制這篇部落格文章
  88. 88. 練習 Website: Test-Crawling-Website/blog 請觀察並透過爬蟲程式下載 ● 網站上每個頁面的標題 (h1) ● 附上 User-Agent
  89. 89. 練習 (進階) Website: Test-Crawling-Website/portfolio 請觀察並透過爬蟲程式下載 ● 下載 2018/01/29 14:39:10 之後 修改過的圖片 ● 附上 User-Agent ● 請依正確格式儲存圖片 備註:要了解 Last-Modified 與 ETag 之間的差異
  90. 90. 網站爬蟲 延伸問題
  91. 91. 網址以外的合法超連結 超連結 <a> tag 的定義並非是一串網址,所以實際爬蟲時 有可能會有不適合送出請求的超連結 除了前面提到的相對路徑以外,我們更應該要注意 <a> tag 中的 href 屬性, 而且 urljoin 的回傳結果並不會過濾
  92. 92. 合法超連結 參考 w3schools 的文件 href value 敘述 範例 absolute URL 絕對路徑 https://gushi.tw relative URL 相對路徑 /ex1/index1.html anchor 其他 tag #top other protocols 其他協定 mailto://example@gmail.com JavaScript 程式碼 javascript:console.log("Hello")
  93. 93. 合法超連結 - anchor anchor 通常使用在同一個頁面的內的超連結, 重複對 anchor 送出請求是多餘的操作 ● 頁面置頂 ● 定位文章標題 ● …
  94. 94. 合法超連結 - Other Protocol Protocol 稱為協定,在網路上各種不同的傳輸都有不同的協定 一般熟知的 http 與 https 就是其中一種,而其他協定也有可以是超連結目標 ● 信箱 mailto:// ● 文件 ftp:// ● …
  95. 95. 合法超連結 - JavaScript JavaScript 是現代網站很常使用的程式語言之一, 雖然不常在網頁中看到這種寫法,但有可能會是反爬蟲手段之一 將 <a> tag 透過 CSS 隱藏,然後在 href 屬性呼叫 JS function, 因為一般使用者看不到不會去點擊, 但如果透過程式對該超連結送出請求,即可判定為爬蟲程式將之拒絕
  96. 96. 程式 - 過濾超連結 我們這邊可以很簡單的透過 regular expression 跟 urlparse 來過濾 練習的話可以參考線上工具做測試 # anchor check1 = re.match('.*#.*', href) # protocol check2 = re.match('[^http|https].*', urlparse(href).scheme) # JS check3 = re.match('javascript.*', href)
  97. 97. 小結 - 路徑處理 關於路徑處理的部份,到目前為止我們知道要考慮 ● 相對路徑轉為絕對路徑 ● 過濾 anchor,others protocol,JavaScript 經過上面這些處理之後,我們確認我們即將要送的是合法的網址 但是實際上在爬蟲時,還有其他方面的因素需要考慮
  98. 98. 目標以外的合法網址 實際上在爬蟲時,根據網站類型的不同,可能還會有以下幾種狀況 ● 內嵌 Youtube 網站影片 ● 內嵌多種社群軟體的連結 ○ 粉絲專頁 ○ 討論區
  99. 99. 目標以外的合法網址 試想你的爬蟲流程,當我們遇到站外網站時 因為是沒有尋訪過的網址,所以程式會試著送出請求 但我們硬體有限,很容易就會造成災難 rootfoundation.datasci.tw image js facebook.com index 我只要爬 foundation.datasci.tw 爬蟲程式會嘗試爬取 facebook.com
  100. 100. 過濾合法網址 我們前面有提到 URL 有其意義,可以使用 urlparse 解析並判斷 <scheme>://<netloc>/<path>;<params>?<query>#<fragment> 目標網站 http://foundation.datasci.tw/ 目標網站粉絲專頁 http://www.facebook.com/twdsconf 上述情況可以透過 urlparse 直接判斷 <netloc> 決定是否爬蟲
  101. 101. 過濾合法網址 但是 urlparse 並無法理解更細節的網域 <scheme>://<netloc>/<path>;<params>?<query>#<fragment> 台大首頁 http://www.ntu.edu.tw/ 台大中文系首頁 http://www.cl.ntu.edu.tw/ 上述範例 <netloc> 會視為不同位置,但實際上這邊可以在細分成 ● 子網域 subdomain ● 網域 domain ● 後綴 suffix
  102. 102. 過濾合法網址 網域有各種種類,e.g. 通用/ 國家地區/ 頂級域名,二級域名等... 網路位置也有各種意義,並無法單純透過 . 將其分割 這邊我們可以使用 tldextract 來協助處理
  103. 103. 練習 Website: http://aiacademy.tw/ 請觀察並透過爬蟲程式下載 ● 紀錄所有 URL ● 附上 User-Agent
  104. 104. 動態網站爬蟲
  105. 105. 靜態與動態網站 靜態網站與動態網站的爬蟲過程基本都一樣,前面提到要注意的地方也都一樣 但是動態網站的意思是網站會透過程式改變結構與內容 請求查看 首頁 請求查看 首頁
  106. 106. 靜態與動態網站 靜態網站與動態網站的爬蟲過程基本都一樣,前面提到要注意的地方也都一樣 但是動態網站的意思是網站會透過程式改變結構與內容 JS render
  107. 107. 靜態與動態網站 一般來說有時間性的網站都會是以動態網站的方式實作,例如電商網站, 像是熱賣商品,搶購商品等等之類的可能每天內容都不一樣 e.g. 蝦皮購物網,全站都是動態網站
  108. 108. 動態網站延伸的額外問題 我們透過瀏覽器看到的靜態網站跟程式抓回來的 HTML 會是一樣的 但是動態網站則不一樣,在程式改變網站結構的過程中會有時間差 動態網站產生的問題 ● 所見非所得 ● 有些需要爬的內容要透過程式改變結構才會出現 ● requests.get 拿到的網頁是還沒被程式更改過的內容
  109. 109. 檢視網站 ● 開發者工具:隨時顯示當前頁面的 HTML (動態網站) ● 檢視網頁原始碼:顯示原始 HTML (靜態網站) 網頁原始碼開發者工具
  110. 110. 檢測網站 可以透過前面提到的差異來判斷網站是否屬於動態網站 ● 比較網頁原始碼與開發者工具顯示的差異 ● 關閉 Chrome javascript 檢查網頁變化 ○ e.g. Quick Javascript Switcher
  111. 111. 動態網站爬蟲 動態網站的問題是程式還沒有更改網站結構就回傳 HTML, 非常直覺的概念就是請網站程式更改完畢再回傳 HTML How ? ● Selenium ● Headless Chrome ● Splash ● … 現在很多服務都可以提供程式 render 後的 HTML,但這邊以 Selenium 介紹為主
  112. 112. Selenium Selenium 其實是網頁自動化測試工具,所以他可以透過程式操作瀏覽器 也因此可以透過瀏覽器取得 render 後的 HTML ● 透過 webdriver 控制各大瀏覽器 ● 因為是模擬瀏覽器操作,所以會比靜態網站爬蟲還要慢 # 假設要透過 Chrome webdriver 操作 Chrome from selenium import webdriver driver = webdriver.Chrome(path_to_webdriver) driver.get(url)
  113. 113. webdriver webdriver 其實是各大瀏覽器自己出的,請注意 Python 操作的 webdriver 版本與系 統上安裝的瀏覽器版本相容 ● Chrome webdriver ● FireFox webdriver ● … 另外要注意的是,控制不同的瀏覽器,selenium 的方法會有些許差異 這份教材會以 Chrome 為範例
  114. 114. Selenium 定位 Tag 我們透過 Selenium 拿到 render 好的 HTML 後有兩種選擇 ● 跟之前一樣把 HTML 傳入 Beautifulsoup 定位 ● 直接用 Selenium 定位 這邊會介紹 Selenium 的定位方式提供參考, 因為其中有 Beautifulsoup 沒有的定位方式
  115. 115. 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)
  116. 116. XPath XPath 是一種類似路徑寫法的定位方式,Selenium 支援但 Beautifulsoup 沒有 語法 意義 / 從 root 的地方開始選擇 // 從任意地方選擇 . 選擇當下這個 node .. 選擇當下這個 node 的 parent node @ 選擇 tag 屬性 * 選擇任何 node | OR
  117. 117. 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]' )
  118. 118. 程式 - 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"]' )
  119. 119. 取得 XPath ● 開發者工具 ○ tag 按右鍵 > Copy > Copy XPath ○ Chrome 與 Firefox 都支援 透過這種方式你會取得只針對該 tag 的 XPath 寫法 如果你是希望根據條件取得所有 tag 可以考慮透過這種方式取得之後再修改
  120. 120. 取得 XPath 這邊會有些可以協助你測試的擴充套件,e.g. XPath helper 測試 XPath 測試結果 XPath Helper 會對符合條件的 tag 做 hightlight
  121. 121. Responsive Web Design (RWD) RWD 是響應式網站設計的縮寫, 設計目的是為了讓網站在不同大小的裝置都可以有很好的使用體驗 因此,在不同大小的裝置中,會呈現不一樣的 HTML
  122. 122. Responsive Web Design (RWD) 為了所見即所得,建議用 selenium 模擬瀏覽器時可以做視窗最大化 from selenium import webdriver driver = webdriver.Chrome(path_to_webdriver) driver.maximize_window()
  123. 123. 程式 - Implicit 請求等待 Selenium 的最常使用的等待有 Implicit 與 Explicit 兩種 Implicit Wait 通常適用於整個 Selenium 程式的預設等待, 當網頁元件暫時找不到時會進行等待,直到 timeout ● 程式執行期間,每次執行尋找指令時都會進行 n 秒等待 ● Implicit wait 在瀏覽器開啟期間都會運作 driver = webdriver.Chrome(path_to_webdriver) driver.implicity_wait(10) # 程式最多等 10 秒
  124. 124. 程式 - Implicit 請求等待 driver = webdriver.Chrome(path_to_webdriver) driver.implicity_wait(10) # 最多等 10 秒 最多等 10 秒就 要回傳 HTML 2 秒就好了,馬 上回傳 請求查看 首頁 請求查看 首頁 首頁 首頁
  125. 125. 程式 - Implicit 請求等待 driver = webdriver.Chrome(path_to_webdriver) driver.implicity_wait(10) # 最多等 10 秒 最多等 10 秒就 要回傳 HTML 10 秒到了,不 管怎樣先回傳 請求查看 首頁 請求查看 首頁 首頁 首頁
  126. 126. 程式 - 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()]' ))
  127. 127. 程式 - Implicit 請求等待 Explicit wait 主要針對特殊元件,或是需要等待某種屬性的狀態等,在精確的等待與 判斷時較適用 e.g. 是否可以被點擊,是否可見 符合條件的 tag 好了 就回傳,最多等十秒 10 秒到了,不 管怎樣先回傳 請求查看 首頁 請求查看 首頁 首頁 首頁
  128. 128. Implicit 與 Explicit 的比較 ● Implicit 實作較簡單,而且只要宣告一次 ● Implicit wait 只作用在 find elements,無法檢查屬性 ● Implicit wait 無法客製化,非預期的顯示時間可能會被忽略 ● Implicit wait 沒有實際定義行為 ● 推荐使用 Explicit wait,雖然實作上會比較複雜 ● 不建議混用 Implicit 與 Explicit wait 參考來源:Implicit Wait 與 Explicit wait 的區別,Selenium with Python
  129. 129. 練習 Website: Test-Crawling-Website/gallery 請觀察並透過爬蟲程式下載 ● 檢查該網站是否為動態網站 ● 透過 Selenium 下載圖片 ● 視窗最大化 ● 設定 Implicit Wait ● 透過 XPath 定位圖片
  130. 130. 練習 Website: http://24h.pchome.com.tw/region/D HBE 請觀察並透過爬蟲程式下載 ● 檢查該網站是否為動態網站 ● 使用 Selenium ● 視窗最大化 ● 設定 Implicit Wait ● 透過 XPath 定位 ● 取得商品的品項與價格資訊
  131. 131. 練習 (進階) 模擬 google search ● https://www.google.com.tw/ ● 搜尋「人工智慧」 ● 紀錄前兩頁搜尋結果的連結 Reference: Selenium Action
  132. 132. 練習 (進階) 簡易下載 reCAPTCHA v1 圖片 ● https://www.google.com/recapt cha/demo/recaptcha ● 使用 Selenium ● 使用 Implicit wait + time.sleep ● 檢查圖片格式 ● 下載五張圖片
  133. 133. Facebook 爬蟲
  134. 134. Graph API 基本概念
  135. 135. Graph API - 不用每次都硬爬一發 為了讓資料有更多的應用,部份公司會提供 API 給予整理好的資料 開發者可以很方便的調用資料,並且可以不用花太多時間擔心反爬蟲策略 Facebook 提供的 API 名稱為 Graph API 代表你的身份,提供給 Facebook 驗證用 搜尋條件 搜尋欄位 搜尋結果
  136. 136. 身份驗證 Access Token 1. Facebook 中的 Access Token 可以代表用戶,粉絲專頁,或是應用程式的身份 2. 我們在送出搜尋的請求時必須附上 Access Token 才會被視為是有效的請求 3. 根據身份的不同,能搜尋到的資料也不同 (權限管理) 一般來說這都會是短期 Token 但可以申請延長到 60 天 若是粉絲專頁甚至可以取得永久 token
  137. 137. 身份驗證 Access Token Access Token 種類有好幾種,使用情境與機制等細節可以參考官方文件 一般來說要爬單一粉絲專頁的內容,使用短期 Access Token 應該就夠了 如果有使用永久粉絲專頁 Access Token 的需求可以參考這個部落格文章
  138. 138. 從官方文件裏面可以得知 Graph 其實就是社交圖表的意涵, 我們可以把他想成社交網路的圖,包含 ● 節點 Nodes,e.g. 用戶 / 相片 / 文件 / 回應等... ● 邊緣 Edges,節點之間的關聯,e.g. 用戶的相片,文件的回應... ● 欄位 Fields,節點的資訊,e.g. 用戶的生日,社團的名稱... 所謂 Graph API 的 Graph
  139. 139. Graph API 版本 API 有多種版本使用,幾乎所有的 API 請求都會往 graph.facebook.com 傳遞,除了 上傳影片的請求以外 ● 每個版本至少在 2 年內都可以使用,並且不會修改 ● 平台變更紀錄,版本詳細資訊
  140. 140. 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}
  141. 141. 取得目標對象身份 以柯文哲粉絲專頁為例,我們要爬專頁內容時,必須先找出該專頁的身份 id 取得網址: https://www.facebook.com/DoctorKoWJ/ 兩者擇一 1. 將網址貼到搜尋條件的欄位 2. 觀察網址,將 DoctorKoWJ 貼到 搜尋條件的欄位 (建議)
  142. 142. 取得目標對象身份 以柯文哲粉絲專頁為例,我們要爬專頁內容時,必須先找出該專頁的身份 id 目標對象身份 id
  143. 143. 取得目標對象身份 如果是以文章為對象的話也一樣,但無法直接將網址貼上搜尋條件欄位取得 id 取得網址: https://www.facebook.com/DoctorKoWJ/v ideos/1213927345375910/ 觀察網址並嘗試可發現文章 id 即為 1213927345375910
  144. 144. 取得留言內容 現今很多粉絲專頁會透過要求留言來與粉絲互動 ● 留言抽獎,Tag 朋友抽獎 ● 留言會給英文單字本,旅遊資訊等... 不論是把留言版當作簽到版,或是單純蒐集留言意見, 取得留言內容一直都是非常頻繁的需求
  145. 145. 取得留言內容 Facebook 留言的 Field name 稱作 comments 當文章是 root node 的時候,comments 指的是文章回覆 comments
  146. 146. 取得留言內容 Facebook 留言的 Field name 稱作 comments 當留言是 root node 的時候,comments 指的是留言回覆 comments
  147. 147. 取得留言內容 - 透過 Graph API 檢視 這邊我們先簡化題目為「爬取文章的所有留言,不包含留言回覆」 假設我們要爬的文章是 https://www.facebook.com/DoctorKoWJ/videos/1213927345375910/ 先透過 Graph API 給定文章 id 搜尋 comment 欄位檢視
  148. 148. 取得留言內容 - 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 )
  149. 149. 取得留言內容 - 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)
  150. 150. 取得留言內容 - 游標型分頁 當我們對文章節點發出 API 請求時, 有可能會因為結果有上千筆,所以系統會做分頁效果回傳給你部份資訊 游標型分頁是有效的分頁方法,但你不應該儲存游標而是每次透過程式決定 游標會指向一個隨機字串, 用來代表清單中的特定項目 判斷是否有下個分頁來決定是否繼續送出請求
  151. 151. 取得留言內容 - 回傳物件 一開始我們送出的請求是要讀取文章資訊 graph.facebook.com/v2.11/{node-id}?field=comments&... 但是當結果太多而回傳分頁資訊時,next 的請求是讀取文章的 comments 資訊 graph.facebook.com/v2.11/{node-id}/comments?...
  152. 152. 取得留言內容 - 回傳物件 因為兩者要求讀取的資訊不同,所以回傳的物件也不同 這邊建議可以一開始就要求讀取文章 comments 資訊 要求讀取文章資訊 要求讀取文章 comments 資訊
  153. 153. 取得留言內容 - 整理資料 最後可以透過 pandas 將資料輸出程表格 再以 csv 或是其他格式儲存,方便之後分析
  154. 154. 練習 https://www.facebook.com/DoctorK oWJ/videos/1213927345375910/ 透過 Graph API 做 Facebook 爬蟲 ● 抓取文章底下的所有留言 ● 輸出成 CSV
  155. 155. 練習 https://www.facebook.com/DoctorK oWJ 透過 Graph API 做 Facebook 爬蟲 ● 抓取粉絲專頁所有文章 ○ 按讚與分享數 ○ 標題與內文 ● 輸出成 CSV
  156. 156. 練習 (進階) https://www.facebook.com/appledail y.tw/posts/10156769966527069 透過 Graph API 做 Facebook 爬蟲 ● 抓取文章所有留言 ○ message ○ attachement ○ application ● 輸出成 CSV

    Soyez le premier à commenter

    Identifiez-vous pour voir les commentaires

  • gbhu168

    Feb. 24, 2018
  • ssuserb2f798

    Feb. 24, 2018
  • EricYang32

    Feb. 24, 2018
  • ssuser32103f

    Feb. 24, 2018
  • JimWhite8

    Feb. 25, 2018
  • chunhsienhsu

    Feb. 25, 2018
  • ssusera24926

    Feb. 25, 2018
  • Pricrowntail

    Feb. 26, 2018
  • ChihChungCheng

    Mar. 9, 2018
  • CurtisChang

    Mar. 16, 2018
  • ChiaohanLee

    Mar. 21, 2018
  • ChihyangLi

    May. 8, 2018
  • LeePeter11

    Jun. 3, 2018
  • joycehsu0111

    Jun. 20, 2018
  • ssuser59044a

    Aug. 19, 2018
  • ChunyiHsiao1

    Nov. 16, 2018
  • KuanTingLiu3

    Nov. 20, 2018
  • aquatear

    Jul. 16, 2020
  • zhihe754

    Sep. 30, 2020
  • EdwinTai1

    Dec. 3, 2020

基本 Python 網路爬蟲教材,可搭配 github 題目練習 https://github.com/afunTW/Python-Crawling-Tutorial 如有發現錯誤,歡迎告知

Vues

Nombre de vues

5 258

Sur Slideshare

0

À partir des intégrations

0

Nombre d'intégrations

116

Actions

Téléchargements

238

Partages

0

Commentaires

0

Mentions J'aime

35

×