BeinYa
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    <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() ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully