<style> .reveal, .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 { font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, "Microsoft JhengHei", Meiryo, "MS ゴシック", "MS Gothic", sans-serif; } h1, h2, h3, h4, h5, h6 { text-transform: none !important; } .color-yellow{ color: yellow; } .alert { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; text-align: left; padding: 10px 0; } .alert-info { color: #31708f; background-color: #d9edf7; border-color: #bce8f1; } .alert-success { color: #3c763d; background-color: #dff0d8; border-color: #d6e9c6; } .alert-danger { color: #a94442; background-color: #f2dede; border-color: #ebccd1; } .reveal { font-size: 28px; } </style> # Dcard網路爬蟲 --- ## 網路爬蟲 #### 網路爬蟲,指的就是自動地在網際網路中進行資料資訊的採集與整理。 其實最笨的方法,我們可以用CTRL+C,CTRL+V的方式,把我們要的網頁資料一個個擷取下來。 <br> ### 如果只有幾十筆還好,但假如有上百筆、上千筆怎麼辦? ---- ## 使用API來爬蟲 API定義了如何請求和接收數據的規則,並提供了一個結構化的方式來訪問數據。 因此可以使用程式語言向API發送HTTP請求,通常是GET或POST請求。這些請求通常包括你的API密鑰、API的URL、參數(例如範圍、搜索關鍵字等)。 最後以JSON格式接收並解析網頁返回的數據,以得到所需的信息。 #### 這種方法更加高效率且可控,因為API通常提供了結構化的數據,而不需要解析整個網頁的HTML。同时,使用API也通常更加合法,因為它是通過官方提供的接口來訪問數據。 ---- ## HTTP(Hypertext Transfer Protocol)<br>超文本傳輸協定 HTTP,可以分為客戶端 (Client) 和伺服器端 (Server),而在這個傳輸協定裡頭,客戶端是主動的,他可以向伺服器發送請求 (Request),相對的伺服器端是被動的,他會根據客戶端的請求來回傳不同的回應 (Response) 1. **GET:** 作用:伺服器請求資源(通常是網頁或數據)。 例子:當你在瀏覽器中輸入網址時,瀏覽器實際上是發送一個GET請求以獲取該網址對應的網頁。 2. **POST:** 作用:向伺服器提交數據。 例子:當你在一個註冊表單中填寫信息並點擊"提交"按鈕時,瀏覽器通常會發送一個POST請求將表單數據發送給伺服器。 **簡而言之,GET用於獲取資源,而POST用於提交數據。** ---- ## Http response (網頁回應) **在伺服器端接收並處理完一個請求後他就會回傳一個response** - 200: success,請求成功 - 302: found,轉址、重新導向 - 400: bad request,伺服器看不懂請求 - 404: not found,找不到網頁 - 500: internal server error,伺服器內部問題 - 505: http version not support,http 版本不支援 當請求成功時,回應的內部就有相應的資料,可能是 html、json 檔、一串文字等 ---- ## Dcard爬蟲的問題 過去,我們可以使用 https://www.dcard.tw/service/api/v2/posts 取得不分類全部文章,等同於抓取 https://www.dcard.tw/f 頁面內文章資訊。 但現今的Dcard api卻被鎖住了,讓我們沒有辦法方便的使用 ![](https://hackmd.io/_uploads/S1r43EpgT.png) ---- ![](https://hackmd.io/_uploads/rJrCKrM-T.png =200x) Cloudflare是一個提供網站安全性提升的服務提供商,它提供了多種方法來識別並阻止機器人(包括惡意機器人)訪問你的網站。 - 使用 CAPTCHA 驗證:要求用戶在訪問頁面時要進行人類驗證。這可以幫助阻止自動化的機器人。 - 設置 IP 阻止規則:封鎖特定機器人的 IP 地址。 ![](https://hackmd.io/_uploads/BJuhtBz-T.png =700x) --- #### 可如果今天網站沒有提供api怎麼辦? # 那我們只能土法煉鋼,<br>找到網頁中,我們要找的目標資料,複製並儲存下來。 ---- ## 瀏覽網頁的過程 - 使用者在瀏覽器中輸入路徑,向伺服器發送請求HTML文件 - 伺服器會回傳對應的HTML文件給瀏覽器 - 瀏覽器解析HTML並產生內容、解析CSS產生排版與樣式、解析JavaScript並產生網頁元素與使用者的互動機制…等等 - 最終,生成我們看到的網頁畫面 ---- ## 網頁由HTML、CSS、JavaScript所組成 - HTML(HyperText Markup Language,超文本標記語言):用來建構網頁的標記式語言,而非程式。 - CSS (Cascading Stylesheets,階層樣式表):一種風格頁面語言,負責建構網頁外觀。 - JavaScript:可以讓網頁中實現複雜的功能,經常用在呈現網頁動態效果。負責描述網頁如何與用戶互動。 ---- ## 什麼是HTML? HTML 是一種標記語言(markup language),而非一般熟知的程式設計語言。它主要用來告訴瀏覽器該如何呈現你的網頁。 HTML 由一個個元素所組成,而元素中包含了標籤(tag)與內容。 ![](https://hackmd.io/_uploads/BJyYmSXWa.png =500x) - 起始標籤:角括弧,也就是符號「`< >`」,裡面需放入元素名稱,如上面的例子「`<p>`」。起始標籤代表這個元素從這裡開始。 - 結束標籤:與起始標籤一樣,只是在元素名稱前面多了個前置斜線「`/`」。內容的最後加上結束標籤,代表這個元素的尾端。 - 內容:這個元素的內容,以上面的例子來說,內容就是這句文字。 - 元素:由起始標籤、結束標籤、內容所組成。 ---- 1. 元素中可以添加「屬性(Attribute)」 ![](https://hackmd.io/_uploads/rkd9mB7bT.png) 屬性能提供更多的資訊,可以利用屬性設定這個元素的色彩、對齊方式、圖表的格線。 <br> 2. 元素裡面還可以放進其他元素 ![image](https://hackmd.io/_uploads/ryZJMFdy1l.png) 這個句子:「我的貓脾氣很暴躁」,若你想強調「very」,我們就可以把「very」用粗體的元素顯示。 <p> My cat is <strong> very </strong> grumpy.</p> ---- ## HTML文件基本結構 ![](https://hackmd.io/_uploads/HkYGybMZa.png) ---- ## HTML標籤 - `<!DOCTYPE html>`:表明文件類型為HTML檔 - `<html>`:用來包裹住整個網頁的內容與設定 - `<head>`:網頁的標頭,經常含有網站名稱、導覽列的訊息等 - `<title>`:頁面標題 - `<body>`:整個網頁的內容資訊 - `<h>`:內容的標題。`<h1>`~`<h6>`的順序決定標題的權重 - `<p>`:用來包裹純文字的段落 - `<a>`:標籤中屬性,例如`<a href="目標網址" target="_blank"(開啟新分頁)>`等。 - `<img>`:圖片。`<img src="圖片網址路徑">` - `<div>`:division,把關聯的物件聚合起來。 ---- ### DOM(Document Object Model,文件物件模型) 把HTML文件內的各個標籤,包括文字、圖片等等都定義成物件,<br>而這些物件最終會形成一個樹狀結構。 ![](https://hackmd.io/_uploads/Hyc0gzfWa.png) 幫助訪問、查找和修改網頁中的元素,因而可以透過程式語言來改變網頁的內容。 ---- ## 節點 如果我們將一個網頁比喻為一棵樹,那麼每個HTML標籤就像是樹上的節點 1. 元素節點(Element Node) 最常見的節點類型,對應於 HTML 中的標籤 例如:`<div>、<p>、<span>` 2. 屬性節點(Attribute Node) 用於提供關於元素的額外資訊,對應於元素的屬性 例如:`class="XXXClass"、id="XXXId"` 3. 文字節點(Text Node) 文字節點表示元素內的文字內容,不包含任何 HTML 標籤 例如,在 `<p>Hello World</p>` 中,Hello World 是一個文字節點 4. 註釋節點(Comment Node) 註釋節點包含檔案中的註釋 例如,HTML 中的 `<!– This is a comment –>` ---- ## 哪裡可以看到HTML 在瀏覽器按下F12,使用開發者模式。或右鍵找到「Inspect」或「檢查」。 想直接完整的看到HTML,也可以直接在網頁中按下右鍵,找到「View page source」或「檢視原始碼」。 ![](https://hackmd.io/_uploads/rJMR6H7-a.png) ---- ## 補充:XML V.S. HTML XML 和 HTML、JSON都同屬於被稱為「**標記語言**」的程式設計語言 兩者同樣用來定義資料的結構、類型、屬性、部件之間的關係 1. HTML 具有預定義的標籤,每個人都必須使用這些標籤(`<header>`、`<p>`),無法建立自己的標籤,相比之下,XML就可使用出於文件用途定義的自訂標籤。 2. HTML 的主要用途是在瀏覽器中以圖形形式顯示以程式語言構建好的文件,所以我們常使用 HTML 來**建立網頁和用戶端 Web 應用程式的**,XML則是允許不同類型的應用程式 (如資料庫) 理解和使用相同的資料及其結構,所以用來在兩個**系統之間交換資料**。 3. XML 可以內嵌在 HTML 中,並用 JavaScript 等語言進行解析以建立動態的網頁。當然,HTML 也可以在必要時內嵌在 XML 中,不過多使用在純文字的資料中。 --- ## 靜態網頁v.s.動態網頁 1. 靜態網頁: - 生成方式:靜態網頁的內容通常是提前生成好,所以每個網頁都是一個單獨的HTML文件。 - 呈現特點:訪問靜態網頁時,伺服器直接將預生成的HTML文件發送給瀏覽器,瀏覽器僅負責解釋和呈現HTML、CSS和JavaScript,無需伺服器端的處理。 - 例子:公司網站介紹、個人履歷等。 2. 動態網頁: - 生成方式:根據用戶的請求和數據庫的內容來動態生成HTML。 - 呈現特點:當用戶訪問動態網頁時,伺服器會根據用戶的請求動態生成HTML,然後將其發送給瀏覽器。 - 例子:社交網站、在線應用程式等。通常是動態網頁,因為它們需要根據用戶的操作和數據來實時更新內容。 ---- ## 靜態網頁 ![](https://hackmd.io/_uploads/rysRYUQbp.png) ---- ## 動態網頁 ![](https://hackmd.io/_uploads/BkLZq87WT.png) --- # 網頁元素定位 ## XPath 與 CSS Selector 定位方式 ---- ## XPath 1. 什麼是XPath? - XPath,全稱為 XML Path Language,是一種用於在 XML 檔案中查詢資訊的語言 - 現代的解析器可以將 HTML 轉換為類似於 XML 的 DOM 結構,因此可以使用 XPath 進行查詢,這使得它成為提取網頁數據的強大工具 - XPath透過路徑表達式(也就是前面提到的分層結構),用於定位 XML 或 HTML 檔案中的節點來選取文件中的「節點」或「節點集」。這些節點可以是元素、屬性、文字等 ---- 2. 常見的 XPath 表達式: - `nodename`:挑選所有名為 `<nodename>` 的節點 - `/` :被稱為絕對路徑定位 當 `/` 用在表達式開頭時,表示查詢從該節點開始 當 `/` 用在兩個節點之間,表示查詢從該節點下的節點開始 * 例如:`/title`:挑選 `<title>` 節點下的所有內容 * 例如:`/html/body`:挑選 `<html>` 節點下 `<body>` 的所有內 - `//`:被稱為相對路徑定位 在整個檔案中進行全面搜索,尋找符合條件的節點,不用擔心它具體在哪一層 * 例如:`//p`:挑選檔案中所有的 `<p>` 節點。 - `[]`:被稱為索引定位 挑選節點中的某個特定的條件,可以是節點的順序或擁有的特定屬性、包含的文字內容 * 例如:`//p[2]`:挑選檔案中第二個 `<p>` 節點 * 例如:`//div[span]`:挑選所有包含特定子元素 `<span>` 的 `<div>` 節點 ---- ### 補充:`//div[span]` V.S. `//div/span` * `//div[span]`:尋找所有包含至少一個 `<span>` 子元素的` <div> `元素 * `//div/span`:所有位於 `<div>` 元素內部的 `<span>` 元素 假如我們有以下的 HTML 檔案結構: ``` <html> <body> <div> <span>第一個 div 的 span</span> </div> <div> <p>第二個 div 的 p 元素,內有 <span>span</span></p> </div> </body> </html> ``` `//div[span]` 匹配結果: ``` <div> <span>第一個 div 的 span</span> </div> ``` `/div/span` 匹配結果: ``` <span>第一個 div 的 span</span> ``` ---- 2. 常見的 XPath 表達式: - `@`:可以用於挑選元素的特定屬性,也可以用於篩選特定條件 * 例如:`//box/@class`:挑選所有有 `class` 屬性的 `<box>` 元素 * 例如:`//box[@class=’red’]`:`[@class=’red’]` 是一個條件,意思是「挑選那些其 `class` 屬性等於 "red" 的 `<box>` 元素」 - `.`:代表「當前正在查看的節點」 - `「..」`在 XPath 表達式中用來挑選任何節點的「父節點」,也就是說,它允許你從當前節點向上移動到其直接上層的節點。 * 當我們有HTML結構,假設當前節點為`/html/body/div` ``` <html> <body> <div> <p>第一段落</p> </body> </html> ``` * 例如:`./p`:會挑選這個 `<div>`元素內的所有` <p>` 元素 * 例如:`../..`:會挑選`<div> `元素上層再上層的`<html>`元素 ---- ## CSS Selector 1. 了解CSS Selector前,先了解CSS - CSS 可以說是針對 HTML 標籤在具現化後,進行外觀調整,所以CSS 需要額外編寫並宣告與 HTML 的選擇關係,這樣瀏覽器才會知道哪個標籤該用怎樣的 CSS 外觀 ![image](https://hackmd.io/_uploads/rJq2W3OJyg.png =400x) - 與 HTML 不同,屬性與值在兩者是不一樣的描述參數: ![image](https://hackmd.io/_uploads/BJknk3Ok1e.png =400x) ---- #### 了解CSS與HTML的關係,要修改目標的外觀,我們要能「定位」目標 2. CSS Selector 介紹 可以簡單理解為:element name(大範圍)、class name(複數範圍)、id name(單一範圍), - `Element Type Selector`:也就是指定常見的標籤作為定位對象 - 例如:`<h1>、<p>、<div>`等 - `Class Selector`:對標籤加上 `class` ,讓特定標籤形成一個群組。而在CSS編譯器裡往往用`「.」`來指向`class` - 例如:![image](https://hackmd.io/_uploads/rJPS6hOJ1l.png =400x) ---- 2. CSS Selector 介紹 - ID Selector:對指定標籤加上 `id` ,`class`可以提供給很多 HTML 標籤使用,但`id`只能有一個。而在CSS編譯器裡往往用`「#」`來指向`id` - 例如:![image](https://hackmd.io/_uploads/rJKeRnOJJx.png) ---- 3. CSS Selector定位 - `.class、#id、element`:選擇特定標籤及屬性下的所有節點 - 例如:`.introl`為選擇`class="intro"`的所有元素 - 例如:`#firstname`為選擇 `id="firstname"` 的所有元素 - 例如:`div > p`為選擇上層為`<div>`元素的所有 `<p>`元素 - 例如:`div + p`為選擇`<div>`元素下層的所有元素 - `[]`:同樣是挑選某個特定的條件、屬性的節點 - 例如:`div[name]`為選擇帶有`name`属性的`<div>`元素 - 例如:`div[name=_keywords]`為選擇`name="_keywords"` 的`<div>`元素 - `nth-child(n)`:特定序列子元素下的所有節點,也就是透過上下層關係來挑選特定節點 - 例如:`p:nth-child(2)`為第二子元素下的所有`<p>`元素 ---- ## XPath 定位 V.S. CSS Selector 定位 總的來說,XPath對於文本的處理上,功能要更全面、更強大;CSS Selector通常語法較為簡潔,運行速度更快些 ![image](https://hackmd.io/_uploads/HyU_DTuJ1l.png) --- ## Selenium 對於爬蟲來說,如果爬取動態網頁內容,只會得到一堆JavaScript程式碼,因為網頁內容需要透過執行JavaScript程式碼後產生。 **Selenium**可以說是自動化控制網路瀏覽器工具,用來**模擬出使用者在瀏覽器的所有操作行為** (點擊按鈕、輸入帳號密碼、捲動捲軸...等),因此除了爬蟲的應用,也常作為「自動化測試」使用的工具,在網站開發完成後,透過自動化的腳本測試所有功能是否正常。 ![](https://hackmd.io/_uploads/B1HIxP7bp.png =500x) ---- ## WebDriver WebDriver可以用來執行並操作瀏覽器的API介面。每一個瀏覽器都會有各自對應的驅動程式(driver),Selenium就可以透過WebDriver來直接對瀏覽器進行操作,對其所支援的瀏覽器進行自動化作業,就如同真的使用者在操作。 ![](https://hackmd.io/_uploads/rkp9gwQ-T.png =800x) --- # 使用R版本的Selenium & WebDriver ---- ### 1. 安裝 RSelenium 套件 ``` r= install.packages("RSelenium") ``` 或是從 GitHub 上面下載最新的版本來安裝: ```r= # 從 GitHub 安裝 RSelenium install.packages("remotes") remotes::install_github("ropensci/RSelenium") ``` <br> ### 2. 安裝wdman套件 提供許多Webdriver和Selenium相關的專案 ``` r= install.packages("wdman") ``` ---- ### 3. 安裝Java https://www.java.com/zh-TW/download/manual.jsp Selenium需要Java輔助執行 ![](https://hackmd.io/_uploads/SylHE8fZa.png =500x) ---- ### 4. 確認並更新Chrome 在Chrome網址中輸入`chrome://version` 進行確認 點選右上角設定 > 說明 > 關於 Google Chrome ,進行更新 ![](https://hackmd.io/_uploads/Hy9qCLzZ6.png) ---- ### 5. 下載ChromeDriver https://googlechromelabs.github.io/chrome-for-testing/ 選擇stable的版本來下載 ![](https://hackmd.io/_uploads/HJxr1vGbp.png) **ChromeDriver網站** https://chromedriver.chromium.org/home ---- ### 6.將ChromeDriver放進專案中 ```r= # install.packages("wdman") # install.packages("RSelenium") library(wdman) library(RSelenium) # 啟用一次selenium(),安裝WebDriver相關專案 # 並且後續要把剛下載的WebDriver,放進專案裡面 selenium() # 這裡會需要跑一段時間來下載專案 # 確認專案位置 chromeCommand <- chrome(retcommand = T, verbose = F, check = F) chromeCommand rm(chromeCommand) gc() #負責回收垃圾的函數,可以釋放不再使用的內存 ``` ---- ### 6.將ChromeDriver放進專案中 找到"C:\\Users\\323be\\AppData\\Local\\binman\\binman_chromedriver\\win32" 大家請記得把"C:\\Users\\{323be}\\..."中的{323be}改成自己的user名字 將ChromeDriver壓縮檔,放到這裡,並且把名字改成剛剛下載的版本 ![](https://hackmd.io/_uploads/r1zECr7Z6.png =300x) ### 這個步驟非常重要↓ 將改好的資料夾(118.0.5993.70,請用最新的版本名稱)中的LICENSE.chromedriver**刪除** ![](https://hackmd.io/_uploads/H15wGvzb6.png =300x) --- ## R的實戰篇 ```r= # 確認版本 binman::list_versions("chromedriver") rD <- rsDriver(browser = "chrome", chromever = "118.0.5993.70", verbose = F) remDr <- rD[["client"]] # 打開瀏覽器 remDr$open() # 關閉瀏覽器 remDr$quit() # 直接退出 remDr$close() # close用於關閉當前會話,也可以用作關閉瀏覽器 rD[["server"]]$stop() # 關掉selenium的server rm(rD) # 删除瀏覽器 rm(remDr) gc() #負責回收垃圾的函數,可以釋放不再使用的內存 # 開啟特定網頁 remDr$navigate("http://www.google.com/ncr") # 取得該網頁url remDr$getCurrentUrl() ``` ---- ## 網頁定位方法 使用WebDriver時很重要的是,需要了解「如何在頁面上查找元素」。也就是如何定位出需要的資料位置。 #### 開發者模式,可以幫助我們查找元素 ![](https://hackmd.io/_uploads/rJdrrjiba.png) ---- ## 定位到網頁上某個物件-findElements函數 這個函數我們主要使用兩個參數,`using`和`value`。 其中`using`:用來指定查找元素用的定位方法。 而`value`則是我們根據`using`方法所搜尋的目標 :::success findElements(using = c("xpath", "css selector", "id", "name", "tag name", "class name", "link text", "partial link text"), value) ::: ---- 1. name:通過元素的 NAME 屬性來查找元素。 2. class name:通過元素的類別名來查找元素。不支持複合類別名。 3. id:通過元素的 ID 屬性來查找元素。 ``` r= # 使用name屬性定位,找到搜尋框 webElem <- remDr$findElement(using = "name", value = "q") webElem$getElementAttribute("name") ## [[1]] ## [1] "q" webElem$getElementAttribute("class") ## [[1]] ## [1] "gLFyf" webElem$getElementAttribute("id") ## [[1]] ## [1] "APjFqb" # 使用class name定位,找到搜尋框 webElem <- remDr$findElement(using = "class name", value = "gLFyf") # 使用id定位,找到搜尋框 webElem <- remDr$findElement(using = "id", value = "APjFqb") ``` ---- 4. link text:通過連結文字來查找元素。 5. partial link text:通過部分連結文字來查找元素。 ``` r= # 使用sendKeysToElements發送關鍵字 webElem$sendKeysToElement(list("網路爬蟲", key = "enter")) # 到網路爬蟲的搜尋頁面 # 使用partial link text,找到有連結的文字元素 webElems <- remDr$findElements(using = "partial link text", value = "網路爬蟲") sapply(webElems, function(x) x$getElementText()) ## [[1]] ## [1] "網路爬蟲- 維基百科,自由的百科全書\n維基百科\nhttps://zh.wikipedia.org › zh-tw" ## [[2]] ## [1] "認識網路爬蟲:爬蟲的應用與原理\nwebscrapingpro.tw\nhttps://www.webscrapingpro.tw › w..." ``` ---- 6. xpath:通過 XPath 運算式來查找元素 7. css selector:通過 CSS 選擇器來查找元素。 ``` r= webElems <- remDr$findElements(using = "css selector", value = "h3.LC20lb.MBeuO.DKV0Md") resHeaders <- unlist(lapply(webElems, function(x) {x$getElementText()})) resHeaders ## [1] "網路爬蟲- 維基百科,自由的百科全書" ## [2] "認識網路爬蟲:爬蟲的應用與原理" webElems <- remDr$findElements(using = "xpath", value = "//*[@id='rso']/div[1]/div/div/div[1]/div/div/span/a/h3") resHeaders <- unlist(lapply(webElems, function(x) {x$getElementText()})) resHeaders [1] "網路爬蟲- 維基百科,自由的百科全書" ``` ---- 8. tag name:通過元素的標籤名來查找元素。 ``` r= remDr$navigate("https://CRAN.r-project.org/") webElems <- remDr$findElements(using = "tag name", "frame") XML::htmlParse(remDr$getPageSource()[[1]]) ## <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> ## <html> ## <head> ## <title>The Comprehensive R Archive Network</title> ## <meta http-equiv="content-type" content="text/html; charset=utf-8"> ## <link rel="icon" href="favicon.ico" type="image/x-icon"> ##<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> ## <link rel="stylesheet" type="text/css" href="R.css"> ## </head> ## <frameset cols="1*, 4*" style="border: none;"> ## <frameset rows="120, 1*"> ## <frame src="logo.html" name="logo" frameborder="0"> ## <frame src="navbar.html" name="contents" frameborder="0"> ## </frameset> ## <frame src="banner.shtml" name="banner" frameborder="0"> ## <noframes> ## <body> ## <h1>The Comprehensive R Archive Network</h1> ## Your browser seems not to support frames, ## here is the <a href="navbar.html">contents page</a> of CRAN. ## </body> ## </noframes> ## </frameset> ## </html> webElems <- remDr$findElements(using = "tag name", "frame") # webElems <- remDr$findElements(using = "xpath", value = "//frame") # using xpath # webElems <- remDr$findElements(using = "css", value = "frame") # using css sapply(webElems, function(x){x$getElementAttribute("src")}) ## [[1]] ## [1] "https://cran.r-project.org/logo.html" ## ## [[2]] ## [1] "https://cran.r-project.org/navbar.html" ## ## [[3]] ## [1] "https://cran.r-project.org/banner.shtml" ``` ---- :::success * getElementText():獲取元素的內部文字(innerText) * sendKeysToElement(sendKeys):這個功能可以用於模擬在網頁上的文字輸入。主要是用來模擬按鍵輸入結果並發送到元素中, * getElementAttribute(attrName):獲取元素的屬性值。 * getPageSource():取得整個網頁的原始程式碼。 ::: ``` r= remDr$navigate("http://www.google.com/ncr") # 使用name屬性定位,找到搜尋框 webElem <- remDr$findElement(using = "name", value = "q") webElem$getElementAttribute("name") ## [[1]] ## [1] "q" # 使用sendKeysToElements發送關鍵字 webElem$sendKeysToElement(list("網路爬蟲", key = "enter")) webElems <- remDr$findElements(using = "partial link text", value = "網路爬蟲") resHeaders <- sapply(webElems, function(x) x$getElementText()) resHeaders ## [[1]] ## [1] "網路爬蟲- 維基百科,自由的百科全書\n維基百科\nhttps://zh.wikipedia.org › zh-tw" remDr$navigate("https://zh.wikipedia.org") WebCrawler <- remDr$getPageSource()[[1]] rvest::read_html(WebCrawler) write(WebCrawler, file = "WebCrawler_wiki.html") ``` --- # 進入到python爬蟲 ---- ## undetected-chromedriver 這是別人幫我們寫好的套件,模擬我們使用chromedriver像是人類,所以可以通過人機測驗 https://github.com/ultrafunkamsterdam/undetected-chromedriver ``` python= # !pip install undetected-chromedriver # 該網站的CAPTCHA驗證測試 import undetected_chromedriver as uc driver = uc.Chrome(headless=True,use_subprocess=False) driver.get('https://nowsecure.nl') driver.save_screenshot('nowsecure.png') ``` ---- ## 程式碼 ### 先來運行套件 ``` python= # !pip install selenium # !pip install webdriver_manager # !pip install undetected_chromedriver from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import time from time import sleep import undetected_chromedriver as uc import json import random import pandas as pd from bs4 import BeautifulSoup ``` ---- ### 開啟瀏覽器 ``` python= #%% # 設定別人提供的 user agent,可以避免被記錄 user_agents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" ] options = uc.ChromeOptions() options.headless = False # 這部分是讓其可以有視窗的呈現 random_user_agent = random.choice(user_agents) options.add_argument(f"--user-agent={random_user_agent}") #隨機抓取一個設定好的 user agent driver = uc.Chrome(options=options, version_main = 129) #%% url = "https://www.dcard.tw/f" driver.get(url) ``` ---- ### 先抓好url ``` python= #%% tmp = [] def extract_links(): try: Mult_links = driver.find_elements( By.CSS_SELECTOR, 'div.d_b1_1w.wdl7s0r') for ClassLink in Mult_links: elementSoup = BeautifulSoup(ClassLink.get_attribute('innerHTML'), 'html.parser') links = elementSoup.find_all('a', class_='d_d8_1hcvtr6 d_cn_2h d_gk_10yn01e d_7v_gdpa86 d_1938jqx_2k d_2zt8x3_1y d_grwvqw_gknzbh d_1ymp90q_1s d_89ifzh_1s d_1hh4tvs_1r d_1054lsl_1r t1gihpsa') for each_link in links: print(each_link['href']) tmp.append(each_link['href']) except Exception as e: print(f"An error occurred: {str(e)}") tmp.append('None') def scroll_and_extract(): innerHeight = 0 offset = 0 count = 0 limit = 1 # 持續捲動直到沒有元素動態產生 while count <= limit: sleep(2) extract_links() offset += 800 driver.execute_script(f''' window.scrollTo({{ top: {offset}, behavior: 'smooth' }});''') # 獲取捲動後的當前總高度 innerHeight = driver.execute_script( 'return window.document.documentElement.scrollHeight;' ) if offset >= innerHeight: count += 1 #%% # 運行 scroll_and_extract() #%% # 存取成txt tmp = list(set(tmp)) #讓資料不會重複 #存取抓到的網址 with open("C:/Users/323be/Documents/just_for_try/dcard_link.txt" , "w")as f: for element in tmp: f.write(str(element) + "\n") ``` ---- ### 存取文字資料(json檔) ``` python= #%% def extract_post(): # 文章標題 title = driver.find_element( By.CSS_SELECTOR, 'div > h1').get_attribute('innerText') # 發文內容 try: txt = driver.find_element(By.CSS_SELECTOR, 'div.d_ma_o2urrm.c1golu5u > div > div').get_attribute( 'innerText').replace('\n', '') except: # 這邊沒改好,還沒找到可能的例外 txt= driver.find_element(By.CSS_SELECTOR,'#__next > div > div.f1og407v > div > div > div > div > article > div.d_ma_o2urrm.c1golu5u > div > a > div > div.m1vig5f1 > div >div').get_attribute('innerText').replace('\n', '') # 發文時間 date = driver.find_element( By.CSS_SELECTOR, 'div.d_d8_2s.d_cn_1v.d_gk_l52nlx.d_7v_7.d_xa_2b.d_lzn0r3_af0gpl.d_du7p7_3j.d_1qszg8z_2m.d_1ti0u92_7 time').text # 提取作者名稱和性別 author_name = "" author_gender = "" author_identifier = "" # 找到 <script type="application/ld+json"> 並提取作者信息 scripts = driver.find_elements(By.XPATH, '//script[@type="application/ld+json"]') for script in scripts: script_text = script.get_attribute('innerText') data = json.loads(script_text) if isinstance(data, list): for item in data: if item.get('@type') == 'SocialMediaPosting' or item.get('@type') == 'DiscussionForumPosting': author_info = item.get('author', {}) author_name = author_info.get('name', '') author_gender = author_info.get('gender', '') author_identifier = author_info.get('identifier', '') break # 找到後即可跳出循環 # 將 "Female" 和 "Male" 轉換為 "女生" 和 "男生" if author_gender == 'Female': author_gender = '女生' elif author_gender == 'Male': author_gender = '男生' # 愛心&留言數量 try: HC_total = driver.find_element( By.XPATH, '//*[@id="__next"]/div[2]/div[2]/div/div/div/div/div[4]/div/div/div[1]').get_attribute('innerText') HC_totals = HC_total.replace('\n', ' ').split(' ') except: HC_total=driver.find_element( By.XPATH,'//*[@id="__next"]/div[2]/div[2]/div/div/div/div/div[5]/div/div/div[1]').get_attribute('innerText') HC_totals = HC_total.replace('\n', ' ').split(' ') # 文章標籤 try: tickets = driver.find_element( By.CSS_SELECTOR, 'meta[name="keywords"]').get_attribute('content') ticket = tickets.replace('\n', ',') except: ticket = 'None' # 存取資料到 result 裡面 result = { '文章標題': title, '作者名稱': author_name, '作者id': author_identifier, '作者性別': author_gender, '發文時間': date, '發文內容': txt, '文章標籤': ticket, '愛心數量': HC_totals[0], '留言數量': HC_totals[-1], '內文留言': '' } results.append(result) def extract_comments(): try: # 定位留言元素 comment_locator = (By.CSS_SELECTOR, 'div[data-key^="comment-"] > div > div > div > div > div > div > span') # 供搜索用: # div.d_b1_1w.d_lc_1u.c19xyhzv # div.d_7v_5.d_cn_1t.d_gk_31.c201erw WebDriverWait(driver, 1).until(EC.presence_of_element_located(comment_locator)) # 找到留言元素 comments = driver.find_elements(*comment_locator) # 提取留言文字內容並添加到集合中 for comment in comments: commentss = comment.get_attribute('innerText') commentsss = commentss.replace('\n', '') tmpcomment.add(commentsss) except Exception as e: # print(f"An error occurred: {e}") # 這邊是可以幫助你檢查發生了什麼錯誤 commentssss = 'None' tmpcomment.add(commentssss) def expand_comments(): while True: # 定位所有「查看其他留言」按鈕 btn_locator = (By.CSS_SELECTOR, 'div[data-key^="subCommentToggle-"] > div > div > div > button') btns = driver.find_elements(*btn_locator) has_more_buttons = False # 用來檢查是否還有更多的按鈕 for btn in btns: str_text = str(btn.text) # 只點擊包含「查看」的按鈕,跳過「隱藏留言」的按鈕 if "查看" in str_text: # 滾動到按鈕可見位置 driver.execute_script("arguments[0].scrollIntoView(true);", btn) sleep(1) # 等待頁面更新 # 嘗試使用 WebDriverWait 等待按鈕可以被點擊 WebDriverWait(driver, 10).until(EC.element_to_be_clickable(btn)) try: # 嘗試正常點擊 btn.click() except: # 使用 JavaScript 點擊按鈕,避免被其他元素遮擋 driver.execute_script("arguments[0].click();", btn) sleep(1) # 等待留言加載 has_more_buttons = True # 標記還有更多按鈕需要點擊 # 如果這次迴圈中沒有點擊到新的按鈕,跳出迴圈 if not has_more_buttons: break # 視窗滑到最後時,幫助你再滑動視窗 def scroll(): innerHeight = driver.execute_script( 'return window.document.documentElement.scrollHeight;' ) innerHeight += 500 driver.execute_script(f''' window.scrollTo({{ top: {innerHeight}, behavior: 'smooth' }}); ''') # 每滑動一次,就獲取一次所有頁面資料 def scroll_and_extract_data(): innerHeight = 0 offset = 0 count = 0 limit = 1 while count <= limit: sleep(1) expand_comments() extract_comments() offset += 500 # 使用JavaScript執行滑動操作,將頁面滑動 driver.execute_script(f''' window.scrollTo({{ top: {offset}, behavior: 'smooth' }}); ''') innerHeight = driver.execute_script( 'return window.document.documentElement.scrollHeight;' ) # 如果滑動的位移超過或等於頁面的高度,則增加計數 if offset >= innerHeight: count += 1 # 存取資料成 json檔 def savejson(filename): link_name = filename.replace("/", "_") with open(f'{folderPath_w}{link_name}.json', "w", encoding='utf-8') as file: file.write(json.dumps( results, ensure_ascii=False, indent=4)) print("檔案", filename, "存好了") #%% tmpcomment = set() results = [] current_height = 0 folderPath_w = 'C:/Users/323be/Documents/just_for_try/' #%% all_links = open('C:/Users/323be/Documents/just_for_try/dcard_link.txt',encoding="UTF-8").readlines() #%% for link in all_links: try: link = link.strip() url = "https://www.dcard.tw" driver.get("{}{}".format(url,link)) # print("{}{}".format(url,link)) sleep(random.randint(1, 3)) extract_post() scroll_and_extract_data() scroll() scroll_and_extract_data() for commentt in tmpcomment: commentt.replace('', 'None') results[0]['內文留言'] = results[0]['內文留言'] + commentt tmpcomment = set() savejson(link) # 清空 tmpcomment 和 results tmpcomment.clear() results.clear() tmpcomment = set() # 將 tmpcomment 還原成空的 set except: print(link,"失敗") continue #%% driver.quit() ```
{"slideOptions":"{\"theme\":\"white\",\"spotlight\":{\"enabled\":false},\"transition\":\"slide\",\"slideNumber\":true}","description":"自動化爬取dcard網站資料","title":"Dcard網路爬蟲","contributors":"[{\"id\":\"23b124fd-e00e-4e8a-b739-5d9c10ed8241\",\"add\":60461,\"del\":34731}]"}
    544 views