---
# System prepended metadata

title: 在爬蟲方面的工作經驗
tags: [interview]

---

---
tags: interview
---

## 更新紀錄
2024-01-13：
- [x] 補充了二面提到的一些問題。

2024-01-11：
- [x] 1. 直接將私人 Repo 都打開了，感覺沒時間整理，而且其實也沒什麼特別的技術。以前開發的時候有些環境變數直接埋在 Code 沒有抽到 .env，請見諒。
- [x] 2. 面試官問到「為了防止重送在前端會採取什麼防禦」，關於「5 秒盾有什麼破解方式」，補充在文末。
- [ ] 3. 應該更詳盡陳述發現並克服過哪些困難。

## 目錄
- [前言](https://hackmd.io/oeH89T61RQOsUZiT-tUYtw?both#%E5%89%8D%E8%A8%80)
- [在爬蟲方面的工作經驗](https://hackmd.io/oeH89T61RQOsUZiT-tUYtw?both#%E5%9C%A8%E7%88%AC%E8%99%AB%E6%96%B9%E9%9D%A2%E7%9A%84%E5%B7%A5%E4%BD%9C%E7%BB%8F%E9%AA%8C)
- [關於爬蟲我覺比較重要的事情](https://hackmd.io/oeH89T61RQOsUZiT-tUYtw?both#%E5%85%B3%E4%BA%8E%E7%88%AC%E8%99%AB%E6%88%91%E8%A7%89%E6%AF%94%E8%BE%83%E9%87%8D%E8%A6%81%E7%9A%84%E4%BA%8B%E6%83%85)
- [面試的問題匯整](https://hackmd.io/oeH89T61RQOsUZiT-tUYtw?both#%E9%9D%A2%E8%AF%95%E7%9A%84%E9%97%AE%E9%A2%98%E6%B1%87%E6%95%B4)


## 前言
~~大部分的作品都擺在私人的 Repo，有些因為年代久遠需要整理，不太好意思直接擺出來讓別人看到，Demo 的時候可以看一下，但請不要驚慌 🙈。~~

## 在爬蟲方面的工作經驗

### 1. 統一發票中獎號碼（2015）
- jsdom（Javascript）

學校的 Android App 作業，我用 PhoneGap（現在叫 Cordova） + jsdom 做了一個獲取中獎號碼有對獎功能的小 App。

### 2. 水情監控 (2016)
- phantom.js（Javascript）

在 Selenium/Puppeteer 出現以前，就用過 phantom.js 獲取水情監控即時影像的截圖。

> 當時有朋友的工作是要定期將多處水情監控的影像匯整成一份日誌。

### 3. 雅虎電影、本日星座運勢、匯率（2017）
- SimpleXML + XPath（PHP）
- Cheerio.js（Javascript）
- File Cache & Throttling（Javascript）

基於好玩做了一個 LINE Bot，在上面整合了本週熱門電影（PHP + SimpleXML + XPath ）、本日星座運勢（Cheerio.js）及近 15 分鐘美金對台幣、台幣對日元匯率查詢的功能。

> 當時用匯率的 API 有每個月的免費額度（忘記是 3000 次還是多少），所以以 15 分鐘為單位作快取避免請求過於頻繁。

### 4. 奧圖碼網站改版（2018）
- Regex（PHP）

將原本靜態頁面內容抓取出來改成以 WordPress 頁面的內容存取。

> 因為涉及到多個語系的網站，且要將取得的頁面內容盡量整合，當時寫的 Regex 較複雜。

### 5. 台北體育館羽球場自動預約爬蟲（2020）
- webdriverio（Javascript）
- tesseract（Javascript）
- jimp（Javascript）
- nexe（Javascript）

https://gitlab.com/z411392/tms-venues-booking

因為網站有 Captcha 保護，而且這時候還不知道可以用 Yolo。破解 Captcha 的方法是用 jimp 作簡單的影像處理，再透過 tesseract 對處理後的 Captcha 作 OCR。

> 成功率感覺 80% 左右，所以後來放著了。

### 6. 台北市、新北市土地公告現值爬蟲（2022、2023）
- webdriverio（Javascript）

https://gitlab.com/z411392/assessed-present-value-for-new-taipei-city
https://gitlab.com/z411392/assessed-present-value-for-taipei-city

一開始先用 webdriverio 開發。

- puppeteer（Javascript）

https://gitlab.com/z411392/apv-new-taipei
https://gitlab.com/z411392/apv-taipei

後來因為改用 puppeteer （考慮到沒有要在 chrome 以外的瀏覽器上執行且寫起來較順手）

- puppeteer（Dart）

https://gitlab.com/z411392/taipei_apv
https://gitlab.com/z411392/new_taipei_apv

最近又實驗性地用 Dart 再改寫過（也能編譯成 binary，有 Stream 類別，但後來也沒用上 😅）。


### 7. 第二類地籍謄本解析器、第二類建物謄本解析器（2022、2023）
- xlsx（Javascript）
- pdf-parse（Javascript）

https://gitlab.com/z411392/land-registration-data-extractor
https://gitlab.com/z411392/building-registration-data-extractor

這邊是很生硬的根據 PDF 內容的格式作解析，然而拿到的 PDF 格式差異可能滿大的，這時候會解析失敗。
涉及到 xlsx 讀取、寫入（批次作業）以及對 PDF 的解析。

- pymupdf（Python）
- python-dotenv（Python）
- openai（Python）
- aiofiles（Python）
- XlsxWriter（Python）

https://gitlab.com/z411392/land_registration_transcripts_parser
https://gitlab.com/z411392/building_registration_transcripts_parser

為了更容易編譯成 binary 後來用 Python 改寫過。
目前正在將擷取內容的工作改成用 OpenAI，而其中一部分 PDF 埋的文字圖片改用雲端的 OCR 服務辨識。
並且打算部署到 Cloud Run，在 Pub/Sub 後執行（分階段）。

### 8. 課程教學影片下載器（2022）
https://gitlab.com/z411392/course-video-saver
- pyppeteer（Python）

這一年我在上勞動部委外的職訓課程。因為課程在一段時間後會下架，所以特別寫了一隻爬蟲將課程影片下載下來。

### 9. 天氣預報爬蟲、PCHome 商品搜尋結果爬蟲（2022）
https://colab.research.google.com/drive/1bted6jeIysqXIfdwyVbL6LBKPZry5jzB
- request（Python）
- beautifulsoup（Python）

課程期間，除了直接用 request 獲取 API 回傳結果外，也學習了用 beautiful soup 解析 HTML 的內容。

#### 10. 影像處理（ 2022）
課程期間學習了簡易的影像處理和文數字辨識（去浮水印、去雜訊、獲取輪廓、影片圖像讀取）。
[去浮水印](https://colab.research.google.com/drive/1Em-Mqn7S_E9bEpvvyfzkJZLurQlJNXFL#scrollTo=miCc3RFL6OsB#cell-miCc3RFL6OsB)、[去雜訊](https://colab.research.google.com/drive/1Em-Mqn7S_E9bEpvvyfzkJZLurQlJNXFL#scrollTo=HqIvd3u6-wqQ#cell-HqIvd3u6-wqQ)、[獲取輪廓](http://cell-iRoJvhwn5Ogq)、[影片圖像讀取](https://colab.research.google.com/drive/1Em-Mqn7S_E9bEpvvyfzkJZLurQlJNXFL#scrollTo=GybXJrffqxMi#cell-h1uMTq3Pqzxo)等。

### 10. Yandex 圖片搜尋結果下載器（2022）
https://gitlab.com/handsome-man-classifier/yandex-crawler
https://gitlab.com/handsome-man-classifier/yandex-downloader
https://gitlab.com/handsome-man-classifier/yandex-cropper
https://gitlab.com/handsome-man-classifier/face-clustering
- aiohttp（Python）
- aiofile（Python）
- cv2（Python）

課程期間，學習了人臉特徵抽取（FaceNet）及 KNN 方法。我自己私下測試了一下各種將人臉特徵向量自動聚類的方法，也測試了 PCA、TSNE 將向量降維量看看測降維後向量相近的程度。

> 後來有試過用隨機投影將特徵向量轉為 16 進位的一串 Hash（但同一個人不同人臉還是可能算出不同 Hash）。

### 11. 聲音轉文字測試（2022）
- moviepy（Python）
- speech_recognition（Python）
- pydub（Python）

https://gitlab.com/z411392/audio-extractor/-/blob/master/audio-extractor.py?ref_type=heads

> 轉換的結果不如預期，所以先擺著。

### 12. 三民輔考考古題下載（2022）
- aiofile、aiohttp（Python）
- beautifulsoup4（Python）
- python-dotenv（Python）
- pygtrie（Python）

https://gitlab.com/z411392/3people-exam-downloader
> 比較特別的是用 trie 當資料結構用來快速尋找考題。

### 13. Google 圖片搜尋結果下載器（2022）
- pupeteer（Python）
https://gitlab.com/z411392/google

課程期間，為了團體專題需要，寫了爬蟲來抓取搜尋結果的圖片。
因為介面上是 Infinite Scroll 的關係，再讀取分頁時要特別花心力寫 Javascript 控制瀏覽器行為以取得內容。

### 14. Excel 密碼破解器、Wifi 密碼破解器（2022）
https://colab.research.google.com/drive/15iogDNnkgoIb8CTHjcJnhO-OEUOHvS-M
https://gitlab.com/z411392/wifi-cracker

> MacOS 切換重試 Wifi 會用比較長的時間，所以整個破解過程非常緩慢。

### 15. instagram 推薦內容下載器（2023）
https://gitlab.com/z411392/instagram_downloader
- puppeteer（Dart）

因為學習了 Dart／Flutter，基於 Dart 的語言特性（有個 Stream 類別）實驗地寫一個爬蟲看看。

> 發現 Mac 上的排程在待機的時候不會執行。而且因為沒有能分別出抓到的圖片哪些是廣告，哪些是推薦的內容所以就擺著了。

### 16. 食品營養資料集（2023）
- Google App Script
- Google Spread Sheet
- ~~OR Tools（Python）~~

https://docs.google.com/spreadsheets/d/1vyMQfFPw45-QUExNZBWBhRfLlnflxYXZD8ObYv9mWQk/edit#gid=1222427181

本來想實驗用 OR Tools 規劃每天的飲食菜單，先用政府公開資料作了一個 Spreadsheet，後來感覺複雜可能沒時間做出來就先放著了。


### 17. 社交 APP（Hornet）人臉資料庫（2023）
- QDrant（向量資料庫）
- oclif（Typescript）
- face-recognition（Python） 
- dependency_injector（Python） 
- aiofile、aiohttp（Python）

https://gitlab.com/z411392/horny_at_cli
https://colab.research.google.com/drive/17Q2JTwWAvWtX3BzoH0Pto-4mbMrikTAH#scrollTo=VucYJcEgC5EW
https://colab.research.google.com/drive/1Gu_Z15oWLPSfDznPS01At47jCtPP6EPs

實驗將人臉辨識得到的特徵向量存到 QDrant 作搜尋。

### 18. 偵測色情圖片（2023）
https://gitlab.com/z411392/nsfw_detector
https://gitlab.com/z411392/open_nsfw

想用這個工具來整理電腦裡面的圖片。

> 發現還是會有偵測錯誤的情形（非色情認定為色情、色情認定為非色情都有）。

### 19. 對 2000 年以來的月報酬率資料作 STL 分解（2023）
https://colab.research.google.com/drive/1omRshwevr7-7he9KfSFjAdDa1BgOAO7Q

雖然沒有時間序列分析的數學基礎，但是簡單的函數呼叫還是會的 🤣。據說這樣作之後能看到排除季節性、雜訊影響的趨勢。

## 關於爬蟲我覺比較重要的事情
1. Python 可以寫 async、await，應該要用 Async Queue 將不同階段的 Producer、Consumer 切分出來，盡量減少無謂的 IO 等待。
2. 因為 GIL 的限制，需要 CPU 的操作例如影像處理、計算，應該要傳遞給 Process Pool 處理。 
3. Javascript／Typescript、Python、Dart 都能寫 Puppeteer 的腳本。Python 和 Dart 比較容易將寫好的程式編譯成 binary。套件的資源在 Python 上還是比較豐富。

## 面試的問題匯整

### 一面 1. 為了防止重送在前端會採取什麼防禦

乍聽之下以為是在介面上應該做什麼（停用表單的發送按鈕），後來想想，問的應該是「作為網站維運方如果想要採取防禦措施應該做什麼事情」。

這時應該做的事情是在表單上附帶一個事先由 Server Side 產生的 Nonce，在傳送表單到 Server Side 時，去檢查這個 Nonce 是否存在、是否正確。

最陽春的做法應該是每個使用者有各自唯一的 Nonce，在某些操作後這個就讓這個 Nonce 更新（要避免窮盡嘗試和暴力破解）。

然而實際上未登入的使用者也可能會面臨表單重送的問題。在這個情形下，頁面要預先產生 Device ID，而基數關係要改為每一個 Device ID 有個各自的 Nonce。

> 之所以要預先產生 Device ID 是因為即便用上 User-Agent 和 Public ID，仍不能保證能產生裝置唯一的識別碼（例如共用一個區域網路使用同一種電腦和同一種瀏覽器）。

如果 Nonce 的數值是完全隨機的（不是由Device ID 和某個 Timestamp 推導出來），就應該另外存在資料庫裡。

已上是我自己先胡思亂想得到的想法。

Stack Overflow 上的[這則討論](https://stackoverflow.com/questions/4145531/how-to-create-and-use-nonces)應該是實現了 Wiki 上的做法：

![image](https://hackmd.io/_uploads/rkLlIACup.png)

這裡可以看到兩個不同的地方：
1. 驗證 Nonce 時，驗證的不是 Nonce 的內容，而是 Nonce 和所傳遞資料的 Hash。

> 其實這邊我有點不明白。想到一個的理由是，如果驗證的不是 Hash，那麼攻擊者可以拿同一串 Nonce 不停的試，直到某天 Server Side 產生的 Nonce 湊巧和攻擊者用的這串 Nonce 相同時（但理論上 Nonce 設得稍微長一點就會讓這件事情很難發生）。

2. 多加了 CNonce，並要求 CNonce 盡量每次都不一樣。
> 這使得即便有了 Nonce 和對一系列偽造內容的 Hash，仍不能從 Hash 反推出偽造的內容是什麼。並且可以監測同一串 CNonce 的使用次數（但是你都能拿到 Nonce 和 Hash 了會拿不到內容嗎🤔）。

### 一面 2. 5 秒盾有什麼破解方式
先看到[這篇](https://34e.cc/1011)，看下來原理是要手動維持跟 CloudFlare 的 Cookie Session 連線。

實際上打開 gitlab 驗面。

在 CloudFlare 驗證結束後，這時候逐一的刪除某個欄位，並且重新打開畫面，看看哪個 Cookie 消失會讓網站將我們重新導向 CloudFlare。這裡發現 `cf_clearance` 可能是關鍵。

![image](https://hackmd.io/_uploads/HkO1fk1KT.png)

使用 Modify Header Value 這樣的外掛，將 `Cookie=cf_clearance=Bxs92Zm2rN7KE.UGRfiWXxZDNX3RNRGA6L.XEM9DrKI-1705075142-0-2-42dbaca.a3861ddb.532ce52a-160.0.0` 放置在 Header 中。清除 Cookies 並重新整理頁面，發現不會再被轉向 CloudFlare 了。
![image](https://hackmd.io/_uploads/rJsJ4J1K6.png)

為了讓自動化作業之後都能夠順利，應該要瞭解整個過程背後，它是如何更新 `cf_clearance` 這個值的。

先打開一個新的分頁，先啓用 Preserve log 功能再貼上網址：
![image](https://hackmd.io/_uploads/r1rsB11tp.png)

![image](https://hackmd.io/_uploads/B1VmD1kKp.png)

![image](https://hackmd.io/_uploads/BkDL_Jkta.png)


這裡發現：
1. 實際上 cf_clearance 是由 gitlab 提供的。
2. CloudFlare 的頁面是一次性的頁面（再次進入就無法瀏覽）。
3. CloudFlare 在完成非爬蟲驗證後，透過 post 方法將驗證結果再回傳給 gitlab。gitlab 應該是再透過 Server To Server 的 API 呼叫驗證並產生 `cf_clearance`。

如此一來產生 cf_clearance 的過程勢必不會是單純的 API 呼叫了。

至於原本文章中提到的 app_session，如果 cf_clearance 欄位是具有時效性，且會在每一個時間後的 Request 自動更新的話，可以透過 Crontab 定期發送 Request 並解析 Cookie 來更新它的值，這樣只有第一次設定值的時候會麻煩些。

不過也可能 cf_clearance 是無法在一段時間後自動更新必須將驗證過程重跑一次的，這時候就只能祈禱通過 CloudFlare 驗證的 Puppeteer 腳本好寫（如果會遇到 Recaptcha 就沒轍 🙈）。

### 二面 1. 在影音平台公司所作的項目擴展都是些什麼？
每個項目的會員機制（要特別加值 VIP）、點數機制（A 類會員不用點數、B 類會員折扣 89 折之類）不太一樣。
也有內容上的不同（例如其他項目都是視頻但這個項目是有聲書）。

### 二面 2. 將資料同步到 Elastic Search 的這一段你們是怎麼實現的？
項目一開始會用 cli 同步過一次，之後每次後台編輯也會同步一次。知道 CDC 的概念（Firebase、Mongo 都能很輕易做到），但是舊項目沒有用上。

不過有個項目同步作業比較複雜，有先將同步工作 Pending 到 Redis，再慢慢執行。

> 回饋：沒有透過 CDC 切分出去影響的倒不是可讀性的問題，而是會影響之後的權責分工。

### 二面 3. Nuxt 和 Vue 有什麼區別？
Nuxt 規劃好一系列的資料夾、檔案的規範或者說擺放方式，極大化減少了 Routing、Compiling 設置工作。

其他⋯說不上來，就是比較方便 🤣

### 二面 4. React Native 和 Flutter 有什麼區別？
React Native 保留了從 Web 過渡過來的一些特性（Web Components、Life Cycle & Hooks）。
Flutter 和 React Native 中間 Rendering 都是 Native 的，除此之外想不到什麼差別 😂。

> 回饋：一個是需要的 Stacks，熟悉 React 較容易從 Web 遷移過來（成本較小？）。另外 React Native 中間還是多了一個轉譯 JS 的過程。

### 二面 5. 在廣告行銷公司開發過什麼樣類型的網站？
主要是各類行銷用的活動網站（抽獎、集點卡），另外有一些會員卡註冊、綁定的申請。

### 二面 6. 應對高流量時活動網站如何做好準備？
最常見的事情是基礎設施例如資料庫最大連線數的設置。
應用層這邊能做的事是讓服務能夠 Scaling（譬如說 Session 應該要存放在共用的 Redis 而不是某個主機上的 File System），每個服務要能 Throttling。
應該設法讓流量盡可能少（壓縮、合併靜態檔案）。

> 回饋：比較想聽的是前面的 Load Balancer 如何安排連線作業給底下的每一台主機。靜態檔案問題倒是容易，直接掛到 CDN 即可。重要的事情是能靜態回應的頁面就不要動用計算資源，我們之前做的抽獎網站是這樣，不曉得你們是不是也這樣做？（沒有，我們直到抵達頁面都還是會持續跟資料庫要資料 😅）

### 二面 7. CRM 跟 ERP 的區別是什麼？
我的印象裡，ERP 比較像進銷存管理的後台，比如說對用球用品的庫存、定價／售價、代理商分潤管理。

CRM 做的事情比較像是設定一些活動，蒐集資料，根據設置的條件派發優惠（集點完成、生日）或通知。

### 二面 8. 分布式爬蟲如何避免重復執行同一任務的問題？
我的想像是 Pub/Sub。Master 透過 Pub 將任外派發出去，Slave 透過 Sub 來執行任務。在我想像中應該是能設定一次 Pub 只會有一個 Slave 收到任務，如果發生錯誤任務無法完成會進入重試過程。但如果真的有可能會遇到同一個任務，應該是在每次任務開始前，先檢查資料庫裡面這個任務的執行狀態，並改為執行中（例如 Pending -> Processing），在任務結束後再將狀態改為其他（例如 Processing -> Finished / Failed）。

### 二面 9. 網站方如何防禦 IP Pool 之類的工具？
我記得 Server Side 應該還做不到區分每一個 Request 來源主機的 Mac Address。以前有過的做法是，透過 Public IP 和 User Agent 去產生 Device ID，然後計算同一個 Device ID 被多少 Public IP 使用以及同一個 Device ID 在某段時間內發出過多少 Requests（但是除非 Device ID 產生過程成本很高，否則其實對防禦 IP Pool 也沒什麼幫助）。

### 二面 10. 如何在 Request Limitation 下盡可能取得更多的資料（更連續、實時）？
要將 Requests 盡可能分散、Concurrently 的發送出去。可能會借助到 Promise Pool 之類的工具（不知道 Python 上叫什麼）。

> 回饋：不一定是什麼工具，要的話自己寫都可以對吧？（沒錯～）