<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Crawler｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/crawler/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>Crawler｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/crawler/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Thu, 01 Oct 2020 09:42:52 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/crawler/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>【Day16】不藏私，加上5行程式就能優化爬蟲的小技巧 ＆ 學會爬蟲，之後呢？</title>
      <link>https://deanlin.net/blog/202010/fb-ig-crawler-day-16/</link>
      <pubDate>Thu, 01 Oct 2020 09:42:52 +0800</pubDate>
      <guid>https://deanlin.net/blog/202010/fb-ig-crawler-day-16/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>1.1 優化分成哪些面向?1.2 優化執行效率及穩定度1.3 背景執行並減少記憶體的消耗</description>
      <media:content url="https://deanlin.net/blog/202010/fb-ig-crawler-day-16/images/cover_hu_1e98c29409e2512e.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>當技術為你的生活帶來便利，他才有存在的意義</p>
</blockquote>
<h2 id="為什麼寫這篇文章">為什麼寫這篇文章<a class="h-anchor" href="#%e7%82%ba%e4%bb%80%e9%ba%bc%e5%af%ab%e9%80%99%e7%af%87%e6%96%87%e7%ab%a0" aria-label="這個段落的連結"></a></h2>
<ol>
<li>如果你跟著這幾天的教學走還是遇到一些莫名其妙的問題，希望在今天的文章有得到你想要的解答</li>
<li>這幾天所教的技巧足以讓你爬下絕大多數的網頁資訊了，<mark>學習爬蟲只是一個過程，我們的目標是希望能靠這些數據給我們帶來便利</mark>，所以今天也分享一些思路給大家</li>
</ol>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-優化爬蟲">1. 優化爬蟲<a class="h-anchor" href="#1-%e5%84%aa%e5%8c%96%e7%88%ac%e8%9f%b2" aria-label="這個段落的連結"></a></h3>
<p>1.1 優化分成哪些面向?</p>
<p>1.2 優化執行效率及穩定度</p>
<p>1.3 背景執行並減少記憶體的消耗</p>
<h3 class="h-nomark" id="2-學會爬蟲之後呢">2. 學會爬蟲，之後呢？<a class="h-anchor" href="#2-%e5%ad%b8%e6%9c%83%e7%88%ac%e8%9f%b2%e4%b9%8b%e5%be%8c%e5%91%a2" aria-label="這個段落的連結"></a></h3>
<hr>
<h2 class="h-nomark" id="1-優化爬蟲-1">1. 優化爬蟲<a class="h-anchor" href="#1-%e5%84%aa%e5%8c%96%e7%88%ac%e8%9f%b2-1" aria-label="這個段落的連結"></a></h2>
<p>昨天我們已經完成爬蟲所需要的功能了，下一步要思考的是如何優化</p>
<h3 class="h-nomark" id="11-優化分成哪些面向">1.1 優化分成哪些面向?<a class="h-anchor" href="#11-%e5%84%aa%e5%8c%96%e5%88%86%e6%88%90%e5%93%aa%e4%ba%9b%e9%9d%a2%e5%90%91" aria-label="這個段落的連結"></a></h3>
<p>前面<a href="https://deanlin.net/blog/202009/fb-ig-crawler-day-13/">【Day13】refactor-重構程式碼，讓合作夥伴對你比讚</a>在討論如何讓你在開發上更有效率，原則上不影響輸出的結果；而今天討論的優化是一個多面向的問題，我把他區分成<mark>顯性及隱性</mark>兩種：</p>
<ul>
<li><strong>顯性</strong>：使用者<mark>肉眼可見</mark>
<ul>
<li>改善使用者操作流程、介面</li>
<li>程式執行過程的畫面優化</li>
</ul>
</li>
<li><strong>隱性</strong>：使用者覺得自己的<mark>電腦變順</mark>了(不過他們通常不會聯想到是程式優化了)
<ul>
<li>更好地執行效率</li>
<li>使用更少的記憶體</li>
<li>執行的穩定性</li>
</ul>
</li>
</ul>
<p>優化是一條沒有盡頭的路，上面我只是提了幾個常見要注意的點而已，接下來我們就來實際優化現在的爬蟲吧～</p>
<hr>
<h3 class="h-nomark" id="12-優化執行效率及穩定度">1.2 優化執行效率及穩定度<a class="h-anchor" href="#12-%e5%84%aa%e5%8c%96%e5%9f%b7%e8%a1%8c%e6%95%88%e7%8e%87%e5%8f%8a%e7%a9%a9%e5%ae%9a%e5%ba%a6" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>遇到的問題</strong></p>
<ol>
<li>在 driver.wait 加上等待時間後偶爾還是會出現登入失敗或是抓不到追蹤人數的問題</li>
<li>在爬蟲的時候會影響到我看 youtube 的流暢程度</li>
</ol>
</li>
<li>
<p><strong>判斷造成的原因</strong></p>
<ol>
<li>本身的<mark>網路速度太慢</mark>，造成網頁過了 driver.wait 的等待時間後依舊還沒完成載入</li>
<li>FB、IG 粉專都是<mark>充滿圖片的頁面</mark>，載入這些資源時消耗太多流量</li>
</ol>
</li>
<li>
<p><strong>優化的方式</strong><br>因為本專案<mark>爬蟲所分析的內容都跟圖片無關，所以只要不載入這些資源</mark>就有以下好處：</p>
<ol>
<li>減少瀏覽器使用流量</li>
<li>減少瀏覽器載入時間</li>
</ol>
<p>想要知道如何不載入圖片，你可以在 Google 搜尋 <strong>selenium chrome參數</strong> ，有很多熱心的大神已經幫你整理好常用的參數了</p>
<ul>
<li>
<p>打開 <strong>initDrive.js</strong> 在 chrome 的 option 加入<mark>不載入圖片</mark>的參數</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>options.addArguments(<span style="color:#a5d6ff">&#39;blink-settings=imagesEnabled=false&#39;</span>)<span style="color:#8b949e;font-style:italic">//不加載圖片提高效率
</span></span></span></code></pre></div><p>加上這段程式後，在專案資料夾的終端機(Terminal)執行指令 <strong>yarn start</strong> ，便可以很明顯的感受到<mark>網頁載入速度變快很多，並且所有圖片都沒有載入</mark>(下圖)</p>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-16/images/fb-ig-crawler-day-16-01.webp" width="1024" height="671" alt="1.2 優化執行效率及穩定度（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-背景執行並減少記憶體的消耗">1.3 背景執行並減少記憶體的消耗<a class="h-anchor" href="#13-%e8%83%8c%e6%99%af%e5%9f%b7%e8%a1%8c%e4%b8%a6%e6%b8%9b%e5%b0%91%e8%a8%98%e6%86%b6%e9%ab%94%e7%9a%84%e6%b6%88%e8%80%97" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>遇到的問題</strong>
<ol>
<li>有時爬蟲執行到一半手賤點一下爬蟲瀏覽器導致程式拋出例外</li>
<li>爬蟲執行時候的畫面一直閃好煩</li>
<li>在記憶體 8G 的電腦執行爬蟲時覺得電腦變得很頓</li>
</ol>
</li>
<li><strong>判斷造成的原因</strong>
<ol>
<li>人有時控制不住自己的手</li>
<li>執行爬蟲時<mark>干擾到使用者的日常工作</mark></li>
<li><mark>chrome 吃太多記憶體</mark></li>
</ol>
</li>
<li><strong>優化的方式</strong><br>在爬蟲每個步驟及功能都穩定後，其實不需跳出瀏覽器視窗來觀察他的動作，<mark>讓瀏覽器在背景執行不但不會干擾使用者工作更能減少記憶體的浪費</mark>
<ul>
<li>
<p>打開 <strong>initDrive.js</strong> 並在 chrome 的 option 加入<mark>不打開瀏覽器視窗</mark>的參數如下</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>options.addArguments(<span style="color:#a5d6ff">&#39;--headless&#39;</span>)<span style="color:#8b949e;font-style:italic">//瀏覽器不提供頁面觀看，linux下如果系統是純文字介面不加這條會啓動失敗
</span></span></span><span style="display:flex;"><span>options.addArguments(<span style="color:#a5d6ff">&#39;--log-level=3&#39;</span>)<span style="color:#8b949e;font-style:italic">//這個option可以讓你跟headless時網頁端的console.log說掰掰
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//下面參數能提升爬蟲穩定性    
</span></span></span><span style="display:flex;"><span>options.addArguments(<span style="color:#a5d6ff">&#39;--disable-dev-shm-usage&#39;</span>)<span style="color:#8b949e;font-style:italic">//使用共享內存RAM
</span></span></span><span style="display:flex;"><span>options.addArguments(<span style="color:#a5d6ff">&#39;--disable-gpu&#39;</span>)<span style="color:#8b949e;font-style:italic">//規避部分chrome gpu bug
</span></span></span></code></pre></div><p>設定完這些後，在專案資料夾的終端機(Terminal)執行指令 <strong>yarn start</strong> ，就會發現<mark>爬蟲變成背景執行，並且電腦順暢許多</mark></p>
</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-學會爬蟲之後呢-1">2. 學會爬蟲，之後呢？<a class="h-anchor" href="#2-%e5%ad%b8%e6%9c%83%e7%88%ac%e8%9f%b2%e4%b9%8b%e5%be%8c%e5%91%a2-1" aria-label="這個段落的連結"></a></h2>
<p><mark>原本我打算用兩天的文章來教取得當日貼文數量的方法，但文章寫完後就刪掉了，因為讀者完全有能力用這幾天所教的技術自行實踐這項功能；我希望用這30天分享更多不同的技術及有幫助的觀念</mark></p>
<blockquote>
<p><strong>授人以魚不如授人以漁</strong></p>
<p>使用模擬瀏覽器來爬蟲我認為是最符合初學者的，你不用去分析複雜的 api，去猜裡面的參數是甚麼意義</p>
<p>你需要做的只是把你平常操作的流程用程式的方式來替你做一遍，然後抓取你所需要的資訊</p>
<p>我寫的文章只是爬蟲的技巧的冰山一角，但只要有了開頭以及研究的方向我相信你是有能力獨立完成其他網頁的爬蟲</p>
</blockquote>
<p>現在人工智慧流行，大數據當道，爬蟲是取得大數據的方法之一，你能透過這些數據分析的東西太多了，我在這裡簡單舉例：</p>
<ul>
<li><strong>追蹤人數爆減/爆增</strong>
<ul>
<li>是因為某一篇貼文引起的嗎?</li>
<li>當天是否有媒體新聞的報導?</li>
<li>粉專經營者是否有特殊決策?</li>
</ul>
</li>
<li><strong>發文頻率與追蹤人數的關係</strong>
<ul>
<li>發文頻率是否與追蹤人數成長正相關</li>
<li>怎麼樣的發文頻率是最恰當的</li>
<li>不同行業的發文頻率比較</li>
</ul>
</li>
<li><strong>貼文按讚與回應</strong>
<ul>
<li>什麼樣的貼文會獲得最多迴響</li>
<li>獲得最多讚的貼文有什麼共同特徵</li>
</ul>
</li>
</ul>
<p>隨便舉的例子都能獨立出來都能成為一篇論文或是商品，<mark>爬蟲是一個很強的武器，但這把武器的強度要看使用者如何發揮他</mark>，我想會看這系列教學文章的讀者心中都有想要爬蟲的目標，開始動手實作吧！</p>
<p><em>再次提醒，技術沒有善惡，人的使用方式才會有善惡，希望這份技術可以幫大家節省時間以及避免重工；如果你把它拿來盜取智慧財產、個人檔案及機敏資訊請自行負擔後續法律責任</em></p>
<hr>
<blockquote>
<p><strong>筆者碎碎念</strong></p>
<p>我希望看完文章的讀者能夠吸收到問題思考的方式，不要只是單純的 copy &amp; paste ，這樣對技術長期來講是在累積負債</p>
<p>如果你是程式的新手，你可以透過修改一些參數來看看結果會有什麼樣的變化，少加哪些參數是不是真的會有錯誤</p>
<p>不要害怕犯錯，因為學習程式的路上幾乎不存在一條完美的道路</p>
<p>你現在滿意的程式往往在幾個月後你會覺得當時怎麼寫的這麼爛</p>
<p>當你誕生出這個想法的時候就是成長了</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="-下個階段">⏭️ 下個階段<a class="h-anchor" href="#-%e4%b8%8b%e5%80%8b%e9%9a%8e%e6%ae%b5" aria-label="這個段落的連結"></a></h3>
<p>關於使用網頁爬蟲的技術文章就分享到今天，接下來我們要來研究 <mark>爬蟲資料如何儲存</mark> ，敬請期待</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day16">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day15_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天json改寫的部分喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整.env檔
<ul>
<li>填上FB登入資訊</li>
<li>填上FB版本(classic/new)</li>
<li>填上IG登入資訊</li>
</ul>
</li>
<li>調整fanspages資料夾內目標爬蟲的粉專網址</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://stackoverflow.max-everyday.com/2019/12/selenium-chrome-options/">selenium啓動Chrome的進階配置參數</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202010/fb-ig-crawler-day-16/images/fb-ig-crawler-day-16-02.webp" width="1024" height="233" alt="參考資源（圖 2）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day15】json x 爬蟲 = 瑣事自動化，生命應該浪費在美好的事情上</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-15/</link>
      <pubDate>Wed, 30 Sep 2020 08:51:22 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-15/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>專案在需求規格中有一條是在自己的電腦執行，在分析完各種檔案格式的優缺點後我決定使用json格式：</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-15/images/cover_hu_2b5138667ef2ac5c.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>即使客戶不懂程式，你也可以讓他學習一點工程師的知識</p>
</blockquote>
<h2 id="我們要請客戶將爬蟲的網址列表填寫到什麼檔案呢">我們要請客戶將爬蟲的網址列表填寫到什麼檔案呢?<a class="h-anchor" href="#%e6%88%91%e5%80%91%e8%a6%81%e8%ab%8b%e5%ae%a2%e6%88%b6%e5%b0%87%e7%88%ac%e8%9f%b2%e7%9a%84%e7%b6%b2%e5%9d%80%e5%88%97%e8%a1%a8%e5%a1%ab%e5%af%ab%e5%88%b0%e4%bb%80%e9%ba%bc%e6%aa%94%e6%a1%88%e5%91%a2" aria-label="這個段落的連結"></a></h2>
<p>專案在需求規格中有一條是<mark>在自己的電腦執行</mark>，在分析完各種檔案格式的優缺點後我決定使用json格式：</p>
<ul>
<li><strong>txt</strong>
<ul>
<li>優點：在所有作業系統中都能夠打開做編輯</li>
<li>缺點：<mark>格式難以律定</mark>，只要使用者多個空格、表點符號&hellip;都容易造成程式的誤判</li>
</ul>
</li>
<li><strong>excel</strong>
<ul>
<li>優點：這是對使用者而言最友善的一種編輯器了，視覺化良好、操作友善</li>
<li>缺點：<mark>不是所有人電腦都有安裝 excel</mark> 編輯器(像我朋友就沒有)</li>
</ul>
</li>
<li><strong>json</strong>
<ul>
<li>優點：在所有作業系統中都能夠打開做編輯，<mark>key-value 的格式易於理解、程式可直接讀取</mark></li>
<li>缺點：使用者需要花幾分鐘時間理解 json 格式的規則</li>
</ul>
</li>
</ul>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-了解-json-格式與基本規則並建立-json-格式的粉專清單">1. 了解 json 格式與基本規則，並建立 json 格式的粉專清單<a class="h-anchor" href="#1-%e4%ba%86%e8%a7%a3-json-%e6%a0%bc%e5%bc%8f%e8%88%87%e5%9f%ba%e6%9c%ac%e8%a6%8f%e5%89%87%e4%b8%a6%e5%bb%ba%e7%ab%8b-json-%e6%a0%bc%e5%bc%8f%e7%9a%84%e7%b2%89%e5%b0%88%e6%b8%85%e5%96%ae" aria-label="這個段落的連結"></a></h3>
<p>1.1 json 格式與基本規則</p>
<p>1.2 在專案中建立 json 格式的粉專清單</p>
<h3 class="h-nomark" id="2-讓程式讀取-json-裡面的資料進行爬蟲">2. 讓程式讀取 json 裡面的資料進行爬蟲<a class="h-anchor" href="#2-%e8%ae%93%e7%a8%8b%e5%bc%8f%e8%ae%80%e5%8f%96-json-%e8%a3%a1%e9%9d%a2%e7%9a%84%e8%b3%87%e6%96%99%e9%80%b2%e8%a1%8c%e7%88%ac%e8%9f%b2" aria-label="這個段落的連結"></a></h3>
<p>2.1 在專案引入 json 檔案</p>
<p>2.2 批量爬蟲程式撰寫</p>
<hr>
<h2 class="h-nomark" id="1-了解-json-格式與基本規則並建立一份-json-格式的粉專清單">1. 了解 json 格式與基本規則，並建立一份 json 格式的粉專清單<a class="h-anchor" href="#1-%e4%ba%86%e8%a7%a3-json-%e6%a0%bc%e5%bc%8f%e8%88%87%e5%9f%ba%e6%9c%ac%e8%a6%8f%e5%89%87%e4%b8%a6%e5%bb%ba%e7%ab%8b%e4%b8%80%e4%bb%bd-json-%e6%a0%bc%e5%bc%8f%e7%9a%84%e7%b2%89%e5%b0%88%e6%b8%85%e5%96%ae" aria-label="這個段落的連結"></a></h2>
<p>今天說的這個部分應該是大部分小編最關心的，要每天手動瀏覽那麼多的粉專想想都很崩潰</p>
<p>所以要來跟大家介紹的一個很棒的文本格式：<strong>json</strong> (JavaScript Object Notation) ，<mark>他能幫助我們批量爬蟲，同時也是網頁 api 最常見的一種回傳格式</mark></p>
<h3 class="h-nomark" id="11-json格式與基本規則">1.1 json格式與基本規則<a class="h-anchor" href="#11-json%e6%a0%bc%e5%bc%8f%e8%88%87%e5%9f%ba%e6%9c%ac%e8%a6%8f%e5%89%87" aria-label="這個段落的連結"></a></h3>
<p>json 可以包含 <code>object (物件)</code> 與 <code>array (陣列)</code></p>
<ul>
<li><strong>物件</strong>
<ul>
<li>
<p>為 key-value 的格式，用<code>{...}</code>包起來</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>{ <span style="color:#a5d6ff">&#34;key&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;value&#34;</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{ <span style="color:#a5d6ff">&#34;姓名&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;寶寶不說&#34;</span> } <span style="color:#8b949e;font-style:italic">//通常用於描寫一項屬性的數值
</span></span></span></code></pre></div></li>
<li>
<p>多個物件使用 , 做為分隔</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>{ <span style="color:#a5d6ff">&#34;key&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;value&#34;</span> , <span style="color:#a5d6ff">&#34;key&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;value&#34;</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{ <span style="color:#a5d6ff">&#34;姓名&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;寶寶不說&#34;</span> , <span style="color:#a5d6ff">&#34;性別&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;不明&#34;</span> } <span style="color:#8b949e;font-style:italic">//多個物件可表達一個東西的多種屬性
</span></span></span></code></pre></div></li>
</ul>
</li>
<li><strong>陣列</strong>
<ul>
<li>
<p>多個 value 之間使用 , 分隔，用<code>[...]</code>包起來</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>[ value, value ]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 陣列與物件裡面value可以使用多種資料型態如下
</span></span></span><span style="display:flex;"><span>[ <span style="color:#a5d6ff">0</span> , { <span style="color:#a5d6ff">&#34;傳奇寶寶&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">null</span> } , <span style="color:#a5d6ff">&#34;string&#34;</span> , <span style="color:#79c0ff">false</span> , [<span style="color:#a5d6ff">&#39;test&#39;</span>] ]
</span></span></code></pre></div></li>
</ul>
</li>
</ul>
<h3 class="h-nomark" id="12-在專案中建立json格式的粉專清單">1.2 在專案中建立json格式的粉專清單<a class="h-anchor" href="#12-%e5%9c%a8%e5%b0%88%e6%a1%88%e4%b8%ad%e5%bb%ba%e7%ab%8bjson%e6%a0%bc%e5%bc%8f%e7%9a%84%e7%b2%89%e5%b0%88%e6%b8%85%e5%96%ae" aria-label="這個段落的連結"></a></h3>
<p><strong>建構邏輯：</strong></p>
<ol>
<li>
<p>我們有<mark>許多粉專需要去追蹤</mark>，所以最外層會用 <code>陣列(array)</code></p>
</li>
<li>
<p>而<mark>每個粉專都有自己的屬性</mark>(title為粉專名稱、url為粉專網址)，所以陣列裡面的 value 採用 <code>物件(object)</code></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;粉專A&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;粉專A連結&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;粉專B&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;粉專B連結&#34;</span>
</span></span><span style="display:flex;"><span>    }  
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div></li>
</ol>
<p><strong>在專案建立的步驟：</strong></p>
<ol>
<li>在專案資料夾中<mark>建立 fanspages 的資料夾</mark>，專門存放粉專頁面爬蟲要使用的json檔案</li>
<li>在 <code>fanspages 資料夾中新增 ig.json、fb.json</code> 這兩個檔案，並依照上述json結構填上你要爬蟲的資訊</li>
<li>你可以先複製下方範例做測試(筆者是以下貼圖的狂熱愛好者，歡迎一起追蹤按愛心XD)</li>
</ol>
<h4 id="igjson">ig.json<a class="h-anchor" href="#igjson" aria-label="這個段落的連結"></a></h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;寶寶不說&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;https://www.instagram.com/baobaonevertell/&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;松尼&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;https://www.instagram.com/sweethouse.sl/&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;麻糬爸&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;https://www.instagram.com/mochi_dad/&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;好想兔&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;https://www.instagram.com/chien_chien0608/&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;title&#34;</span>: <span style="color:#a5d6ff">&#34;ㄇㄚˊ幾兔&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;url&#34;</span>: <span style="color:#a5d6ff">&#34;https://www.instagram.com/machiko324/&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><hr>
<h2 class="h-nomark" id="2-讓爬蟲程式讀取-json-裡面的資料">2. 讓爬蟲程式讀取 json 裡面的資料<a class="h-anchor" href="#2-%e8%ae%93%e7%88%ac%e8%9f%b2%e7%a8%8b%e5%bc%8f%e8%ae%80%e5%8f%96-json-%e8%a3%a1%e9%9d%a2%e7%9a%84%e8%b3%87%e6%96%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-在專案引入-json-檔案">2.1 在專案引入 json 檔案<a class="h-anchor" href="#21-%e5%9c%a8%e5%b0%88%e6%a1%88%e5%bc%95%e5%85%a5-json-%e6%aa%94%e6%a1%88" aria-label="這個段落的連結"></a></h3>
<ul>
<li>引入 json 檔案時請注意相對路徑，建議先用 <code>console.log 確認是否有正確引入</code></li>
<li>因為這個檔案是人工撰寫，可能會不小心複製到重複粉專，所以使用 <code>filter 來過濾</code></li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">let</span> fanpage_array <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;../fanspages/ig.json&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//過濾掉重複的粉專頁面，減少資源浪費
</span></span></span><span style="display:flex;"><span>fanpage_array <span style="color:#ff7b72;font-weight:bold">=</span> fanpage_array.filter((fanpage, index, self) =&gt;
</span></span><span style="display:flex;"><span>    index <span style="color:#ff7b72;font-weight:bold">===</span> self.findIndex(f =&gt; f.url <span style="color:#ff7b72;font-weight:bold">===</span> fanpage.url)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>console.log(fanpage_array)
</span></span></code></pre></div><h3 class="h-nomark" id="22-批量爬蟲程式撰寫">2.2 批量爬蟲程式撰寫<a class="h-anchor" href="#22-%e6%89%b9%e9%87%8f%e7%88%ac%e8%9f%b2%e7%a8%8b%e5%bc%8f%e6%92%b0%e5%af%ab" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>將前往粉專頁面與爬蟲的部分用 <code>for/of迴圈</code> 包起來(因迴圈內用到 <strong>async/await</strong>)，讓他依序前往粉專頁面抓資料</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (fanpage <span style="color:#ff7b72">of</span> fanpage_array) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 撰寫你要對fanpage做的事       
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
<li>
<p>我們要當個好爬蟲，每個頁面設定爬蟲停留 3-6 秒，<mark>不要因為自己的爬蟲造成別人伺服器的負擔</mark></p>
<blockquote>
<p>這裡我們使用Math.random()取得區間亂數</p>
</blockquote>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.sleep((Math.floor(Math.random()<span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">4</span>)<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">3</span>)<span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">1000</span>)
</span></span></code></pre></div></li>
<li>
<p>統整後完整程式如下</p>
<h4 id="crawlerigjs">crawlerIG.js<a class="h-anchor" href="#crawlerigjs" aria-label="這個段落的連結"></a></h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">let</span> fanpage_array <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;../fanspages/ig.json&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//過濾掉重複的粉專頁面，減少資源浪費
</span></span></span><span style="display:flex;"><span>fanpage_array <span style="color:#ff7b72;font-weight:bold">=</span> fanpage_array.filter((fanpage, index, self) =&gt;
</span></span><span style="display:flex;"><span>    index <span style="color:#ff7b72;font-weight:bold">===</span> self.findIndex(f =&gt; f.url <span style="color:#ff7b72;font-weight:bold">===</span> fanpage.url)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> ig_username <span style="color:#ff7b72;font-weight:bold">=</span> process.env.IG_USERNAME
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> ig_userpass <span style="color:#ff7b72;font-weight:bold">=</span> process.env.IG_PASSWORD
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { By, until } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>)
</span></span><span style="display:flex;"><span>exports.crawlerIG <span style="color:#ff7b72;font-weight:bold">=</span> crawlerIG;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> crawlerIG (driver) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> isLogin <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> loginInstagram(driver, By, until)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (isLogin) {
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#a5d6ff">`IG開始爬蟲`</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> (fanpage <span style="color:#ff7b72">of</span> fanpage_array) { <span style="color:#8b949e;font-style:italic">// 用迴圈爬蟲每個粉專
</span></span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">let</span> trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">null</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">const</span> isGoFansPage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> goFansPage(driver, fanpage.url)
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">if</span> (isGoFansPage) {
</span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic">//每個頁面爬蟲停留3~6秒，不要造成別人的伺服器負擔
</span></span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">await</span> driver.sleep((Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">4</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">3</span>) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">1000</span>)
</span></span><span style="display:flex;"><span>                trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> getTrace(driver, By, until)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">if</span> (trace <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#79c0ff">null</span>) {
</span></span><span style="display:flex;"><span>                console.log(<span style="color:#a5d6ff">`</span><span style="color:#a5d6ff">${</span>fanpage.title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">無法抓取追蹤人數`</span>)
</span></span><span style="display:flex;"><span>            } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>                console.log(<span style="color:#a5d6ff">`</span><span style="color:#a5d6ff">${</span>fanpage.title<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">追蹤人數：</span><span style="color:#a5d6ff">${</span>trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>.<span style="color:#8b949e;font-style:italic">//後面程式一樣
</span></span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span></code></pre></div></li>
</ul>
<hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
<li>
<p>確認畫面執行順序如你預期：Instagram自動登入 → 依序跳轉到指定帳號 → Facebook自動登入 → 依序跳轉到粉絲頁 → 關閉</p>
</li>
<li>
<p>大概裝完一壺水的時間後，你就能看到 FB &amp; IG 的追蹤人數嚕～</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-15/images/fb-ig-crawler-day-15-01.webp" width="1024" height="422" alt="執行程式（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
</ol>
<p>搭配上json後有沒有覺得自己功力大增XD，上面的是IG的範例，大家可以自己嘗試看看FB部分的如何改寫喔～</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day15">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day14_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天json改寫的部分喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整.env檔
<ul>
<li>填上FB登入資訊</li>
<li>填上FB版本(classic/new)</li>
<li>填上IG登入資訊</li>
</ul>
</li>
<li>調整fanspages資料夾內目標爬蟲的粉專網址</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://www.footmark.info/programming-language/javascript/json-format-and-javascript/">JSON 格式與 JavaScript 解析教學範例</a></li>
<li><a href="https://ithelp.ithome.com.tw/articles/10197904">用Math.random()取得亂數的技巧</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-15/images/fb-ig-crawler-day-15-02.webp" width="1024" height="233" alt="參考資源（圖 2）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day13】重構程式碼，減少歷史業障</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-13/</link>
      <pubDate>Mon, 28 Sep 2020 08:45:12 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-13/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>昨天我們完成了一個跑得動的程式，但很明顯這不是一個好的程式 大家應該有感受到隨著程式越來越長，我們對他的掌握力逐步下降 根據 Clean Code 的原則而言就是說這程式寫得很爛XD 很多人為了盡快完成功能而埋頭狂寫，但這樣容易導致你日後維護以及交接的困難性，你所負責的專案越大這…</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-13/images/cover_hu_5d125fe20b2659b6.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>數據顯示，工程師完成最後 20% 的工作時間跟之前的 80% 一樣多</p>
</blockquote>
<h2 id="完成功能後重構是另一個開始">完成功能後，重構是另一個開始<a class="h-anchor" href="#%e5%ae%8c%e6%88%90%e5%8a%9f%e8%83%bd%e5%be%8c%e9%87%8d%e6%a7%8b%e6%98%af%e5%8f%a6%e4%b8%80%e5%80%8b%e9%96%8b%e5%a7%8b" aria-label="這個段落的連結"></a></h2>
<p>昨天我們完成了一個<mark>跑得動的程式</mark>，但很明顯這<mark>不是一個好的程式</mark></p>
<p>大家應該有感受到隨著程式越來越長，我們對他的掌握力逐步下降</p>
<p>根據 Clean Code 的原則而言就是說這<strong>程式寫得很爛XD</strong></p>
<p>很多人為了盡快完成功能而埋頭狂寫，但這樣容易導致你日後維護以及交接的困難性，你所負責的專案越大這個感受會更明顯</p>
<p>所以在完成功能後<mark>重構是必要的任務，他能提升你日後解決 bug、需求變更的工作效率</mark></p>
<p>接下來就以昨天程式為範例來重構吧！</p>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<p>了解 &amp; 修正目前專案所犯下的錯誤</p>
<ol>
<li>主程式做了太多事情</li>
<li>一個函式做了太多事情</li>
<li>物件宣告的方式太複雜且多次被使用</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-主程式做了太多事情">1. 主程式做了太多事情<a class="h-anchor" href="#1-%e4%b8%bb%e7%a8%8b%e5%bc%8f%e5%81%9a%e4%ba%86%e5%a4%aa%e5%a4%9a%e4%ba%8b%e6%83%85" aria-label="這個段落的連結"></a></h2>
<p>主程式就像是專案的總經理，他只需要知道大方向的架構；而架構裡面的細節就交給各部門處理，他只是溝通資料的橋樑</p>
<p>根據上述的邏輯，我們應該把在主程式中的函式轉移到另外的資料夾(ex:tools)，依據功能去命名檔名，範例如下:</p>
<ol>
<li>主程式 - index.js</li>
<li>初始化瀏覽器 - tools/initDrive.js</li>
<li>Facebook爬蟲 - tools/crawlerFB.js</li>
<li>Instagram爬蟲 - tools/crawlerIG.js</li>
</ol>
<p>在這樣的分工後我們的主程式是不是變得很乾淨呢？</p>
<h3 id="indexjs">index.js<a class="h-anchor" href="#indexjs" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>require(<span style="color:#a5d6ff">&#39;dotenv&#39;</span>).config(); <span style="color:#8b949e;font-style:italic">//載入.env環境檔
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { initDrive } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;./tools/initDrive.js&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { crawlerFB } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;./tools/crawlerFB.js&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { crawlerIG } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;./tools/crawlerIG.js&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> crawler () {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> initDrive();
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>driver) {<span style="color:#8b949e;font-style:italic">//driver不存在就結束程式
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//因為有些人是用FB帳號登入IG，為了避免增加FB登出的動作，所以採取先對IG進行爬蟲
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> crawlerIG(driver)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> crawlerFB(driver)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    driver.quit();
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>crawler()
</span></span></code></pre></div><hr>
<h2 class="h-nomark" id="2-一個函式做了太多事情">2. 一個函式做了太多事情<a class="h-anchor" href="#2-%e4%b8%80%e5%80%8b%e5%87%bd%e5%bc%8f%e5%81%9a%e4%ba%86%e5%a4%aa%e5%a4%9a%e4%ba%8b%e6%83%85" aria-label="這個段落的連結"></a></h2>
<p><mark>記住一個大原則：一個函式只需要做一件事情！</mark></p>
<p>現在我們把 FB 登入並取得追蹤人數寫在同一隻函式，這樣會增加你日後維護的困難度，因為<code>函式越長，你越難除錯(debug)</code>；以 <code>loginFacebookGetTrace</code> 這隻函式舉例，它實際上可以解構成好幾個功能：</p>
<ol>
<li>取得 Facebook(經典版/新版) 的爬蟲路徑 - <strong>getCrawlerPath</strong></li>
<li>Facebook爬蟲 - <strong>crawlerFB</strong></li>
<li>登入Facebook - <strong>loginFacebook</strong></li>
<li>前往粉絲頁 - <strong>goFansPage</strong></li>
<li>取得追蹤人數 - <strong>getTrace</strong></li>
</ol>
<p>把每個功能獨立成函式，你就能輕鬆除錯(debug)。下面程式以 FB 爬蟲作為解構範例，你可以自己練習改寫 IG 的部分喔</p>
<h3 id="crawlerfbjs">crawlerFB.js<a class="h-anchor" href="#crawlerfbjs" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_username <span style="color:#ff7b72;font-weight:bold">=</span> process.env.FB_USERNAME
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_userpass <span style="color:#ff7b72;font-weight:bold">=</span> process.env.FB_PASSWORD
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { By, until } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>) <span style="color:#8b949e;font-style:italic">// 從套件中取出需要用到的功能
</span></span></span><span style="display:flex;"><span>exports.crawlerFB <span style="color:#ff7b72;font-weight:bold">=</span> crawlerFB;<span style="color:#8b949e;font-style:italic">//讓其他程式在引入時可以使用這個函式
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// FB有經典版以及新版的區分，兩者的爬蟲路徑不同，我們藉由函式取得各自的路徑
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { fb_head_path, fb_trace_path } <span style="color:#ff7b72;font-weight:bold">=</span> getCrawlerPath();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> getCrawlerPath () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (process.env.FB_VERSION <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">&#39;new&#39;</span>) {<span style="color:#8b949e;font-style:italic">//如果是新版FB
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;fzdkajry&#34;)]`</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_trace_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;knvmm38d&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {<span style="color:#8b949e;font-style:italic">//如果為設定皆默認為舊版
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;_1vp5&#34;)]`</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_trace_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[@id=&#34;PagesProfileHomeSecondaryColumnPagelet&#34;]//*[contains(@class,&#34;_4bl9&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> crawlerFB(driver) {    
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> loginFacebook(driver)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fanpage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://www.facebook.com/baobaonevertell/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> goFansPage(driver, fanpage)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.sleep(<span style="color:#a5d6ff">3000</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> getTrace(driver)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> loginFacebook(driver) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> web <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;https://www.facebook.com/login&#39;</span>;<span style="color:#8b949e;font-style:italic">//我們要前往FB
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(web)<span style="color:#8b949e;font-style:italic">//在這裡要用await確保打開完網頁後才能繼續動作
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//填入fb登入資訊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_email_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;email&#34;]`</span>)));
</span></span><span style="display:flex;"><span>    fb_email_ele.sendKeys(fb_username)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_pass_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;pass&#34;]`</span>)));
</span></span><span style="display:flex;"><span>    fb_pass_ele.sendKeys(fb_userpass)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//抓到登入按鈕然後點擊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> login_elem <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;loginbutton&#34;]`</span>)))
</span></span><span style="display:flex;"><span>    login_elem.click()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//因為登入這件事情要等server回應，你直接跳轉粉絲專頁會導致登入失敗
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//用登入後才有的元件，來判斷是否登入
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(fb_head_path)))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> goFansPage(driver, web_url) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//登入成功後要前往粉專頁面
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(web_url)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> getTrace(driver) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> fb_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;<span style="color:#8b949e;font-style:italic">//這是紀錄FB追蹤人數
</span></span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//因為考慮到登入之後每個粉專顯示追蹤人數的位置都不一樣，所以就採用全抓在分析    
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_trace_eles <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.
</span></span><span style="display:flex;"><span>        wait(until.elementsLocated(By.xpath(fb_trace_path)))
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">const</span> fb_trace_ele <span style="color:#ff7b72">of</span> fb_trace_eles) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> fb_text <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> fb_trace_ele.getText()
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (fb_text.includes(<span style="color:#a5d6ff">&#39;人在追蹤&#39;</span>)) {
</span></span><span style="display:flex;"><span>            fb_trace <span style="color:#ff7b72;font-weight:bold">=</span> fb_text.replace(<span style="color:#79c0ff">/\D/g</span>, <span style="color:#a5d6ff">&#39;&#39;</span>)<span style="color:#8b949e;font-style:italic">//只取數字
</span></span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">`FB追蹤人數：</span><span style="color:#a5d6ff">${</span>fb_trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 class="h-nomark" id="3-物件宣告的方式太複雜且多次被使用">3. 物件宣告的方式太複雜且多次被使用<a class="h-anchor" href="#3-%e7%89%a9%e4%bb%b6%e5%ae%a3%e5%91%8a%e7%9a%84%e6%96%b9%e5%bc%8f%e5%a4%aa%e8%a4%87%e9%9b%9c%e4%b8%94%e5%a4%9a%e6%ac%a1%e8%a2%ab%e4%bd%bf%e7%94%a8" aria-label="這個段落的連結"></a></h2>
<p>我們要將<mark>建立瀏覽器的部分獨立成一隻js</mark>，原因如下</p>
<ul>
<li>建立瀏覽器前需要做事前確認及複雜的設定</li>
<li>這個瀏覽器作為物件會被多次使用</li>
<li>如果瀏覽器的參數需要變更可以在一個檔案集中管理</li>
</ul>
<p>基於上述原因，我們<strong>應該把他獨立出來</strong>，這樣你只需要<code>在一個檔案管理你宣告的物件(瀏覽器)設定，而主程式很單純的使用回傳的物件(瀏覽器)即可</code></p>
<h3 id="initdrivejs">initDrive.js<a class="h-anchor" href="#initdrivejs" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>exports.initDrive <span style="color:#ff7b72;font-weight:bold">=</span> initDrive;<span style="color:#8b949e;font-style:italic">//讓其他程式在引入時可以使用這個函式
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> webdriver <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>) <span style="color:#8b949e;font-style:italic">// 加入虛擬網頁套件
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> chrome <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver/chrome&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> options <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.Options();
</span></span><span style="display:flex;"><span>options.setUserPreferences({ <span style="color:#a5d6ff">&#39;profile.default_content_setting_values.notifications&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1</span> });<span style="color:#8b949e;font-style:italic">//因為FB會有notifications干擾到爬蟲，所以要先把它關閉
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> path <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;path&#39;</span>);<span style="color:#8b949e;font-style:italic">//用於處理文件路徑的小工具
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;fs&#34;</span>);<span style="color:#8b949e;font-style:italic">//讀取檔案用
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> initDrive () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>checkDriver()) {<span style="color:#8b949e;font-style:italic">// 檢查Driver是否是設定，如果無法設定就結束程式
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 建立這個broswer的類型
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).withCapabilities(options).build();
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//考慮到ig在不同螢幕寬度時的Xpath不一樣，所以我們要在這裡設定統一的視窗大小
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.manage().window().setRect({ width<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1280</span>, height<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">800</span>, x<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">0</span>, y<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">0</span> });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> driver
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> checkDriver() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        chrome.getDefaultService()<span style="color:#8b949e;font-style:italic">//確認是否有預設
</span></span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> {
</span></span><span style="display:flex;"><span>        console.warn(<span style="color:#a5d6ff">&#39;找不到預設driver!&#39;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//&#39;../chromedriver.exe&#39;記得調整成自己的路徑
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> file_path <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;../chromedriver.exe&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//請確認印出來日誌中的位置是否與你路徑相同
</span></span></span><span style="display:flex;"><span>        console.log(path.join(__dirname, file_path));
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//確認路徑下chromedriver.exe是否存在            
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (fs.existsSync(path.join(__dirname, file_path))) {
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">const</span> service <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.ServiceBuilder(path.join(__dirname, file_path)).build();<span style="color:#8b949e;font-style:italic">//設定driver路徑
</span></span></span><span style="display:flex;"><span>            chrome.setDefaultService(service);
</span></span><span style="display:flex;"><span>            console.log(<span style="color:#a5d6ff">&#39;設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>        } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>            console.error(<span style="color:#a5d6ff">&#39;無法設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>這篇文章是以自己的程式作為範例講解，如果你想更深入了解重構請閱讀<a href="https://medium.com/%E5%BE%8C%E7%AB%AF%E6%96%B0%E6%89%8B%E6%9D%91/%E7%AD%86%E8%A8%98-%E9%87%8D%E6%A7%8B-chapter-1-2-%E7%AC%AC%E4%B8%80%E5%80%8B%E7%AF%84%E4%BE%8B-%E9%87%8D%E6%A7%8B%E5%8E%9F%E5%89%87-ca57a6d40f42">重構—改善既有的程式設計</a>，他深入淺出說明重構的原則讓我受益良多</p>
<blockquote>
<p>程式碼往往沒有所謂最好的寫法，只有當下最合理的選擇。而需求永遠在變動，就算當下重構到很滿意，可能下個月再回來看又覺得不夠了。 - YH Yu</p>
</blockquote>
<hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
<li>
<p>觀察執行順序：Instagram 自動登入 → 跳轉到指定帳號 → Facebook自動登入 → 跳轉到粉絲頁 → 關閉</p>
</li>
<li>
<p>確定與程式重構後能順利取得FB &amp; IG的追蹤人數</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-13/images/fb-ig-crawler-day-13-01.webp" width="1024" height="159" alt="執行程式（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
</ol>
<p>如果你還有什麼問題或是覺得有可以改善的地方歡迎在下方留言討論</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day13">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day12_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天的重構喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整.env檔
<ul>
<li>填上FB登入資訊</li>
<li>填上FB版本(classic/new)</li>
<li>填上IG登入資訊</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://blog.camel2243.com/2017/06/24/nodejs-module-exports-%E8%88%87-exports-%E7%9A%84%E5%B7%AE%E5%88%A5/">[Nodejs] module.exports 與 exports 的差別</a></li>
<li><a href="https://medium.com/%E5%BE%8C%E7%AB%AF%E6%96%B0%E6%89%8B%E6%9D%91/%E7%AD%86%E8%A8%98-%E9%87%8D%E6%A7%8B-chapter-1-2-%E7%AC%AC%E4%B8%80%E5%80%8B%E7%AF%84%E4%BE%8B-%E9%87%8D%E6%A7%8B%E5%8E%9F%E5%89%87-ca57a6d40f42">重構—改善既有的程式設計</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-13/images/fb-ig-crawler-day-13-02.webp" width="1024" height="233" alt="參考資源（圖 2）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day12】合體吧！用一隻程式搞定FB、IG爬蟲</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-12/</link>
      <pubDate>Sun, 27 Sep 2020 07:59:57 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-12/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>1.1 瀏覽器關閉導致無法讀取網頁元件1.2 跨網域(CORS)錯誤</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-12/images/cover_hu_f487751a291cc221.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>有些需求不是複製貼上就能解決的</p>
</blockquote>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-分析實作過程中可能會遇到的問題">1. 分析實作過程中可能會遇到的問題<a class="h-anchor" href="#1-%e5%88%86%e6%9e%90%e5%af%a6%e4%bd%9c%e9%81%8e%e7%a8%8b%e4%b8%ad%e5%8f%af%e8%83%bd%e6%9c%83%e9%81%87%e5%88%b0%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p>1.1 瀏覽器關閉導致無法讀取網頁元件</p>
<p>1.2 跨網域(CORS)錯誤</p>
<h3 class="h-nomark" id="2-一隻程式爬完-fb--ig-粉專">2. 一隻程式爬完 FB &amp; IG 粉專<a class="h-anchor" href="#2-%e4%b8%80%e9%9a%bb%e7%a8%8b%e5%bc%8f%e7%88%ac%e5%ae%8c-fb--ig-%e7%b2%89%e5%b0%88" aria-label="這個段落的連結"></a></h3>
<p>2.1 分析程式結構邏輯</p>
<p>2.2 思考程式上有什麼地方需要改進</p>
<hr>
<h2 class="h-nomark" id="1-實作過程中可能會遇到的問題">1. 實作過程中可能會遇到的問題<a class="h-anchor" href="#1-%e5%af%a6%e4%bd%9c%e9%81%8e%e7%a8%8b%e4%b8%ad%e5%8f%af%e8%83%bd%e6%9c%83%e9%81%87%e5%88%b0%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-瀏覽器關閉導致無法讀取網頁元件">1.1 瀏覽器關閉導致無法讀取網頁元件<a class="h-anchor" href="#11-%e7%80%8f%e8%a6%bd%e5%99%a8%e9%97%9c%e9%96%89%e5%b0%8e%e8%87%b4%e7%84%a1%e6%b3%95%e8%ae%80%e5%8f%96%e7%b6%b2%e9%a0%81%e5%85%83%e4%bb%b6" aria-label="這個段落的連結"></a></h3>
<p>如果你直接把兩天文章複製貼上爬的話一定會看到這個錯誤訊息</p>
<pre tabindex="0"><code>WebDriverError: element not interactable
</code></pre><ul>
<li>這是因為<strong>一次跳出兩個瀏覽器</strong>在執行，只要其中一個瀏覽器完成爬蟲任務觸發 <strong>driver.quit()</strong> 就會導致另一個還沒爬蟲完畢的<strong>瀏覽器因無法讀取元件而掛掉</strong></li>
<li>面對這個問題，我建議<strong>開一個瀏覽器</strong>來處理：
<ol>
<li>因為<mark>跑爬蟲會消耗電腦的記憶體以及網路流量</mark>，我希望電腦跑爬蟲的時候不要影響我做其他事情的效率</li>
<li>使用一個瀏覽器在跑<mark>遇到問題時方便除錯</mark></li>
</ol>
</li>
</ul>
<h3 class="h-nomark" id="12-跨網域cors錯誤">1.2 跨網域(CORS)錯誤<a class="h-anchor" href="#12-%e8%b7%a8%e7%b6%b2%e5%9f%9fcors%e9%8c%af%e8%aa%a4" aria-label="這個段落的連結"></a></h3>
<p>如果你把他<strong>改成一個瀏覽器</strong>執行，跑完 FB 粉專再跑 IG 粉專應該又會出現<strong>跨網域(CORS)問題</strong>的錯誤(目前只有 windows 作業系統才會遇到)</p>
<ul>
<li>
<p>這是<mark>網域切換時因為一些安全性的疑慮而拒絕跳轉</mark>，像是從 <a href="https://www.facebook.com/">https://www.facebook.com/</a> 跳轉到 <a href="https://www.instagram.com/">https://www.instagram.com</a> 就會遇到這個問題</p>
</li>
<li>
<p>這個問題在建立瀏覽器時加上設定 <code>{ acceptSslCerts: true, acceptInsecureCerts: true }</code> 即可解決</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).withCapabilities(options,
</span></span><span style="display:flex;"><span>    { acceptSslCerts<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">true</span>, acceptInsecureCerts<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">true</span> }<span style="color:#8b949e;font-style:italic">//這是為了解決跨網域問題
</span></span></span><span style="display:flex;"><span>).build();
</span></span></code></pre></div></li>
</ul>
<hr>
<h2 class="h-nomark" id="2-一隻程式爬完-fb--ig-粉專-1">2. 一隻程式爬完 FB &amp; IG 粉專<a class="h-anchor" href="#2-%e4%b8%80%e9%9a%bb%e7%a8%8b%e5%bc%8f%e7%88%ac%e5%ae%8c-fb--ig-%e7%b2%89%e5%b0%88-1" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-分析程式結構邏輯">2.1 分析程式結構邏輯<a class="h-anchor" href="#21-%e5%88%86%e6%9e%90%e7%a8%8b%e5%bc%8f%e7%b5%90%e6%a7%8b%e9%82%8f%e8%bc%af" aria-label="這個段落的連結"></a></h3>
<p>為了幫助讀者能較快掌握程式的邏輯，我根據每個函式所做的功能以及執行步驟列出結構如下：</p>
<ul>
<li><code>crawler</code> : 觸發爬蟲的函式
<ol>
<li>
<p>檢查 Driver 是否是設定</p>
</li>
<li>
<p>建立這個 browser 的類型</p>
</li>
<li>
<p>設定 browser 的視窗大小</p>
</li>
<li>
<p><code>loginInstagramGetTrace</code> : 登入IG並取得指定帳號的追蹤人數</p>
<blockquote>
<p>先執行 IG 爬蟲是因為有些人登入 IG 的方式是綁定 FB 帳號，這會導致我們程式需要判斷更多狀況；而讓IG先爬蟲就能避免這個問題嚕～</p>
</blockquote>
<ol>
<li>前往 IG 登入頁，填入登入資訊</li>
<li>點擊登入按紐</li>
<li>判斷是否登入成功</li>
<li>登入成功後跳轉指定帳號頁面</li>
<li>獲取指定帳號追蹤人數</li>
</ol>
</li>
<li>
<p><code>loginFacebookGetTrace</code> : 登入 FB 並取得粉絲專業的追蹤人數</p>
<ol>
<li>前往 FB 登入頁，填入登入資訊</li>
<li>點擊登入按紐</li>
<li>判斷是否登入成功</li>
<li>登入成功後跳轉指定粉專頁面</li>
<li>獲取指定粉專追蹤人數</li>
</ol>
</li>
<li>
<p>關閉 browser</p>
</li>
</ol>
</li>
</ul>
<blockquote>
<p>寫程式如同拼積木，隨著功能的增加程式的體積會越來越大，這裡我建議你寫一份程式架構圖的文件來輔助，未來遇到需求變更時看架構圖就能迅速修改</p>
</blockquote>
<h4 id="indexjs">index.js<a class="h-anchor" href="#indexjs" aria-label="這個段落的連結"></a></h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>require(<span style="color:#a5d6ff">&#39;dotenv&#39;</span>).config(); <span style="color:#8b949e;font-style:italic">//載入.env環境檔
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> path <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;path&#39;</span>);<span style="color:#8b949e;font-style:italic">//用於處理文件路徑的小工具
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;fs&#34;</span>);<span style="color:#8b949e;font-style:italic">//讀取檔案用
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//取出.env檔案的FB、IG資訊
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> ig_username <span style="color:#ff7b72;font-weight:bold">=</span> process.env.IG_USERNAME
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> ig_userpass <span style="color:#ff7b72;font-weight:bold">=</span> process.env.IG_PASSWORD
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_username <span style="color:#ff7b72;font-weight:bold">=</span> process.env.FB_USERNAME
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_userpass <span style="color:#ff7b72;font-weight:bold">=</span> process.env.FB_PASSWORD
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> webdriver <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>), <span style="color:#8b949e;font-style:italic">// 加入虛擬網頁套件
</span></span></span><span style="display:flex;"><span>    By <span style="color:#ff7b72;font-weight:bold">=</span> webdriver.By,<span style="color:#8b949e;font-style:italic">//你想要透過什麼方式來抓取元件，通常使用xpath、css
</span></span></span><span style="display:flex;"><span>    until <span style="color:#ff7b72;font-weight:bold">=</span> webdriver.until;<span style="color:#8b949e;font-style:italic">//直到抓到元件才進入下一步(可設定等待時間)
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> chrome <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver/chrome&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> options <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.Options();
</span></span><span style="display:flex;"><span>options.setUserPreferences({ <span style="color:#a5d6ff">&#39;profile.default_content_setting_values.notifications&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1</span> });<span style="color:#8b949e;font-style:italic">//因為FB會有notifications干擾到爬蟲，所以要先把它關閉
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> getCrawlerPath () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (process.env.FB_VERSION <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">&#39;new&#39;</span>) {<span style="color:#8b949e;font-style:italic">//如果是新版FB
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;fzdkajry&#34;)]`</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_trace_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;knvmm38d&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {<span style="color:#8b949e;font-style:italic">//如果為設定皆默認為舊版
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;_1vp5&#34;)]`</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_trace_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[@id=&#34;PagesProfileHomeSecondaryColumnPagelet&#34;]//*[contains(@class,&#34;_4bl9&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> loginFacebookGetTrace (driver) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> web <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;https://www.facebook.com/login&#39;</span>;<span style="color:#8b949e;font-style:italic">//我們要前往FB
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(web)<span style="color:#8b949e;font-style:italic">//在這裡要用await確保打開完網頁後才能繼續動作
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//填入fb登入資訊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_email_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;email&#34;]`</span>)));
</span></span><span style="display:flex;"><span>    fb_email_ele.sendKeys(fb_username)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_pass_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;pass&#34;]`</span>)));
</span></span><span style="display:flex;"><span>    fb_pass_ele.sendKeys(fb_userpass)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//抓到登入按鈕然後點擊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> login_elem <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;loginbutton&#34;]`</span>)))
</span></span><span style="display:flex;"><span>    login_elem.click()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// FB有經典版以及新版的區分，兩者的爬蟲路徑不同，我們藉由函式取得各自的路徑
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> { fb_head_path, fb_trace_path } <span style="color:#ff7b72;font-weight:bold">=</span> getCrawlerPath();
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//因為登入這件事情要等server回應，你直接跳轉粉絲專頁會導致登入失敗
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//用登入後才有的元件，來判斷是否登入
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(fb_head_path)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//登入成功後要前往粉專頁面
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fanpage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://www.facebook.com/baobaonevertell/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(fanpage)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> fb_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;<span style="color:#8b949e;font-style:italic">//這是紀錄FB追蹤人數
</span></span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//因為考慮到登入之後每個粉專顯示追蹤人數的位置都不一樣，所以就採用全抓在分析
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_trace_eles <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementsLocated(By.xpath(fb_trace_path)))
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">const</span> fb_trace_ele <span style="color:#ff7b72">of</span> fb_trace_eles) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> fb_text <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> fb_trace_ele.getText()
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (fb_text.includes(<span style="color:#a5d6ff">&#39;人在追蹤&#39;</span>)) {
</span></span><span style="display:flex;"><span>            fb_trace <span style="color:#ff7b72;font-weight:bold">=</span> fb_text
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">`FB追蹤人數：</span><span style="color:#a5d6ff">${</span>fb_trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> loginInstagramGetTrace (driver) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> web <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;https://www.instagram.com/accounts/login&#39;</span>;<span style="color:#8b949e;font-style:italic">//前往IG登入頁面
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(web)<span style="color:#8b949e;font-style:italic">//在這裡要用await確保打開完網頁後才能繼續動作
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//填入ig登入資訊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> ig_username_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.css(<span style="color:#a5d6ff">&#34;input[name=&#39;username&#39;]&#34;</span>)));
</span></span><span style="display:flex;"><span>    ig_username_ele.sendKeys(ig_username)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> ig_password_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.css(<span style="color:#a5d6ff">&#34;input[name=&#39;password&#39;]&#34;</span>)));
</span></span><span style="display:flex;"><span>    ig_password_ele.sendKeys(ig_userpass)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//抓到登入按鈕然後點擊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> login_elem <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.css(<span style="color:#a5d6ff">&#34;button[type=&#39;submit&#39;]&#34;</span>)))
</span></span><span style="display:flex;"><span>    login_elem.click()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//登入後才會有右上角功能列，我們以這個來判斷是否登入
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;react-root&#34;]//*[contains(@class,&#34;_47KiJ&#34;)]`</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//登入成功後要前往粉專頁面
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fanpage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://www.instagram.com/baobaonevertell/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(fanpage)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> ig_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;<span style="color:#8b949e;font-style:italic">//這是紀錄IG追蹤人數
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> ig_trace_xpath <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">`//*[@id=&#34;react-root&#34;]/section/main/div/header/section/ul/li[2]/a/span`</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> ig_trace_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(ig_trace_xpath)))
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// ig因為當人數破萬時文字不會顯示，所以改抓title
</span></span></span><span style="display:flex;"><span>    ig_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> ig_trace_ele.getAttribute(<span style="color:#a5d6ff">&#39;title&#39;</span>)
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">`IG追蹤人數：</span><span style="color:#a5d6ff">${</span>ig_trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> checkDriver() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        chrome.getDefaultService()<span style="color:#8b949e;font-style:italic">//確認是否有預設
</span></span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> {
</span></span><span style="display:flex;"><span>        console.warn(<span style="color:#a5d6ff">&#39;找不到預設driver!&#39;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//&#39;../chromedriver.exe&#39;記得調整成自己的路徑
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> file_path <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;../chromedriver.exe&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//請確認印出來日誌中的位置是否與你路徑相同
</span></span></span><span style="display:flex;"><span>        console.log(path.join(__dirname, file_path));
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//確認路徑下chromedriver.exe是否存在            
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (fs.existsSync(path.join(__dirname, file_path))) {
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic">//設定driver路徑
</span></span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">const</span> service <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.ServiceBuilder(path.join(__dirname, file_path)).build();
</span></span><span style="display:flex;"><span>            chrome.setDefaultService(service);
</span></span><span style="display:flex;"><span>            console.log(<span style="color:#a5d6ff">&#39;設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>            console.error(<span style="color:#a5d6ff">&#39;無法設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> crawler () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>checkDriver()) {<span style="color:#8b949e;font-style:italic">// 檢查Driver是否是設定，如果無法設定就結束程式
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 建立這個browser的類型
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).withCapabilities(options).build();
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//考慮到ig在不同螢幕寬度時的Xpath不一樣，所以我們要在這裡設定統一的視窗大小
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.manage().window().setRect({ width<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1280</span>, height<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">800</span>, x<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">0</span>, y<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">0</span> });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//因為有些人是用FB帳號登入IG，為了避免增加FB登出的動作，所以採取先對IG進行爬蟲
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> loginInstagramGetTrace(driver)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> loginFacebookGetTrace(driver)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    driver.quit();
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>crawler()
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="22-思考程式上有什麼地方需要改進">2.2 思考程式上有什麼地方需要改進<a class="h-anchor" href="#22-%e6%80%9d%e8%80%83%e7%a8%8b%e5%bc%8f%e4%b8%8a%e6%9c%89%e4%bb%80%e9%ba%bc%e5%9c%b0%e6%96%b9%e9%9c%80%e8%a6%81%e6%94%b9%e9%80%b2" aria-label="這個段落的連結"></a></h3>
<p>看完上面的程式後，我想大部分的人都有點暈了XD，如果你有認真看程式應該有以下體會：</p>
<ol>
<li>閱讀起來有點吃力</li>
<li>遇到錯誤時不好找問題</li>
<li>很難掌握每個函式的功能</li>
<li>幾個月後回來看這隻程式估計一臉茫然</li>
</ol>
<p>如果還有更多的體會歡迎大家在下方留言(請鞭小力一點QQ)</p>
<p>明天會講<a href="https://deanlin.net/blog/202009/fb-ig-crawler-day-13/">程式碼的重構</a>，透過重構我們可以更有效率的掌握程式；後天會講<a href="https://deanlin.net/blog/202009/fb-ig-crawler-day-14/">try-catch在本專案的應用</a>，方便日後的除錯</p>
<hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
<li>
<p>觀察執行順序是否為：Instagram自動登入 → 跳轉到指定帳號 → Facebook自動登入 → 跳轉到粉絲頁 → 關閉</p>
<blockquote>
<p>如果想要中斷終端機(Terminal)執行的程式，可以用下面按鍵組合:</p>
<ul>
<li>Windows: Ctrl + c</li>
<li>Mac: control + c</li>
</ul>
</blockquote>
</li>
<li>
<p>順利的話就能看到FB &amp; IG的追蹤人數嚕～</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-12/images/fb-ig-crawler-day-12-01.webp" width="1024" height="155" alt="執行程式（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
</ol>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day12">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day11_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天的功能喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整.env檔
<ul>
<li>填上FB登入資訊</li>
<li>填上FB版本(classic/new)</li>
<li>填上IG登入資訊</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-12/images/fb-ig-crawler-day-12-02.webp" width="1024" height="233" alt="ℹ️ 專案原始碼（圖 2）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day11】舉一反三，帶你了解IG爬蟲不可忽略的細節</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-11/</link>
      <pubDate>Sat, 26 Sep 2020 08:44:52 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-11/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>有了 Facebook 爬蟲的經驗後，我相信這篇大家是有能力獨自完成的，建議大家先用自己的方式來完成今日目標，這篇文章適合實作遇到問題或是你實作完成後再來參考</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/cover_hu_30d62b86535ec880.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>學習，從複製開始</p>
</blockquote>
<h2 id="筆者有話先說">筆者有話先說<a class="h-anchor" href="#%e7%ad%86%e8%80%85%e6%9c%89%e8%a9%b1%e5%85%88%e8%aa%aa" aria-label="這個段落的連結"></a></h2>
<p>有了 Facebook 爬蟲的經驗後，我相信這篇大家是有能力獨自完成的，<mark>建議大家先用自己的方式來完成今日目標</mark>，這篇文章適合實作遇到問題或是你實作完成後再來參考</p>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-完成-ig-自動登入">1. 完成 IG 自動登入<a class="h-anchor" href="#1-%e5%ae%8c%e6%88%90-ig-%e8%87%aa%e5%8b%95%e7%99%bb%e5%85%a5" aria-label="這個段落的連結"></a></h3>
<p>1.1 分析 IG 登入頁面的元件</p>
<p>1.2 改用 css 抓取元件，完成 IG 自動登入</p>
<h3 class="h-nomark" id="2-從畫面判斷使用者是否登入成功">2. 從畫面判斷使用者是否登入成功<a class="h-anchor" href="#2-%e5%be%9e%e7%95%ab%e9%9d%a2%e5%88%a4%e6%96%b7%e4%bd%bf%e7%94%a8%e8%80%85%e6%98%af%e5%90%a6%e7%99%bb%e5%85%a5%e6%88%90%e5%8a%9f" aria-label="這個段落的連結"></a></h3>
<h3 class="h-nomark" id="3-前往-ig-帳號並取得追蹤人數">3. 前往 IG 帳號並取得追蹤人數<a class="h-anchor" href="#3-%e5%89%8d%e5%be%80-ig-%e5%b8%b3%e8%99%9f%e4%b8%a6%e5%8f%96%e5%be%97%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8" aria-label="這個段落的連結"></a></h3>
<p>3.1 了解響應式網頁對爬蟲的影響</p>
<p>3.2 設定瀏覽器開啟時的視窗大小</p>
<p>3.3 完成前往 IG 帳號並取得追蹤人數的程式</p>
<hr>
<h2 class="h-nomark" id="1-ig-自動登入">1. IG 自動登入<a class="h-anchor" href="#1-ig-%e8%87%aa%e5%8b%95%e7%99%bb%e5%85%a5" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-分析-ig-登入頁面的元件">1.1 分析 IG 登入頁面的元件<a class="h-anchor" href="#11-%e5%88%86%e6%9e%90-ig-%e7%99%bb%e5%85%a5%e9%a0%81%e9%9d%a2%e7%9a%84%e5%85%83%e4%bb%b6" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>我在反覆使用 <a href="https://www.instagram.com/accounts/login">IG 登入</a> 時發現<mark>登入的畫面偶爾會長不一樣</mark>(如下圖)導致Xpath路徑錯誤</p>
<table>
  <thead>
      <tr>
          <th>版本1</th>
          <th>版本2</th>
      </tr>
  </thead>
  <tbody>
  </tbody>
</table>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-01.webp" width="1400" height="1267" alt="Imgur" fetchpriority="high" decoding="async"></p>
<p>|</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-02.webp" width="1400" height="1267" alt="Imgur" loading="lazy" decoding="async"></p>
</li>
<li>
<p>先<mark>進入開發者模式</mark>確認紅框內元件的特徵</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-03.webp" width="1400" height="1267" alt="Imgur" loading="lazy" decoding="async"></p>
<ul>
<li>在下圖我們觀察到 <strong>電話號碼、用戶名稱或電子郵件</strong> 以及 <strong>密碼</strong> 這兩個元件的 <strong>共通點</strong>：
<ol>
<li>
<p>都是 <code>input 的 html 標籤</code></p>
</li>
<li>
<p>用 <code>name 這個 attribute</code> 來表示他們的作用</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-04.webp" width="968" height="142" alt="1.1 分析 IG 登入頁面的元件（圖 4）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-05.webp" width="946" height="142" alt="1.1 分析 IG 登入頁面的元件（圖 5）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li><strong>登入按鈕</strong> 則用以下條件來取出：
<ol>
<li>
<p>使用 <code>button 的 html 標籤</code></p>
</li>
<li>
<p>用 <code>type 這個 attribute</code> 來表示他要執行的動作</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-06.webp" width="1024" height="302" alt="1.1 分析 IG 登入頁面的元件（圖 6）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-改用-css-抓取元件完成-ig-自動登入">1.2 改用 css 抓取元件，完成 IG 自動登入<a class="h-anchor" href="#12-%e6%94%b9%e7%94%a8-css-%e6%8a%93%e5%8f%96%e5%85%83%e4%bb%b6%e5%ae%8c%e6%88%90-ig-%e8%87%aa%e5%8b%95%e7%99%bb%e5%85%a5" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>根據上面的分析後我們<mark>改用 css 抓取 IG 登入的元件</mark>來操作</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//填入ig登入資訊
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> ig_username_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.css(<span style="color:#a5d6ff">&#34;input[name=&#39;username&#39;]&#34;</span>)));
</span></span><span style="display:flex;"><span>ig_username_ele.sendKeys(ig_username)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> ig_password_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.css(<span style="color:#a5d6ff">&#34;input[name=&#39;password&#39;]&#34;</span>)));
</span></span><span style="display:flex;"><span>ig_password_ele.sendKeys(ig_userpass)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//抓到登入按鈕然後點擊
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> login_elem <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.css(<span style="color:#a5d6ff">&#34;button[type=&#39;submit&#39;]&#34;</span>)))
</span></span><span style="display:flex;"><span>login_elem.click()
</span></span></code></pre></div><blockquote>
<ul>
<li>
<p>別忘記在 .env檔填寫 IG登入的帳號密碼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#填寫自己登入IG的真實資訊(建議開小帳號來實驗，因為帳號使用太頻繁會被官方鎖住)</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">IG_USERNAME</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;ig username&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">IG_PASSWORD</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;ig password&#39;</span>
</span></span></code></pre></div></li>
</ul>
</blockquote>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-判斷使用者是否登入成功">2. 判斷使用者是否登入成功<a class="h-anchor" href="#2-%e5%88%a4%e6%96%b7%e4%bd%bf%e7%94%a8%e8%80%85%e6%98%af%e5%90%a6%e7%99%bb%e5%85%a5%e6%88%90%e5%8a%9f" aria-label="這個段落的連結"></a></h2>
<ul>
<li>
<p>與 FB 類似，<mark>IG 要登入之後才會有右上角功能列</mark>，這裡我使用他作為判斷登入與否</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-07.webp" width="1024" height="1013" alt="2. 判斷使用者是否登入成功（圖 7）" loading="lazy" decoding="async"></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//登入後才會有右上角功能列，我們以這個來判斷是否登入
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;react-root&#34;]//*[contains(@class,&#34;_47KiJ&#34;)]`</span>)))
</span></span></code></pre></div></li>
</ul>
<hr>
<h2 class="h-nomark" id="3-抓出-ig-追蹤人數">3. 抓出 IG 追蹤人數<a class="h-anchor" href="#3-%e6%8a%93%e5%87%ba-ig-%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-了解響應式網頁對爬蟲的影響">3.1 了解響應式網頁對爬蟲的影響<a class="h-anchor" href="#31-%e4%ba%86%e8%a7%a3%e9%9f%bf%e6%87%89%e5%bc%8f%e7%b6%b2%e9%a0%81%e5%b0%8d%e7%88%ac%e8%9f%b2%e7%9a%84%e5%bd%b1%e9%9f%bf" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>響應式網頁設計(RWD)是什麼？</strong>：網頁會根據瀏覽器解析度的不同重新排版，同一個網頁你在手機、平板、電腦看到的畫面配置都是不一樣的</li>
<li>如果你跟我一樣充滿實驗精神，你會發現 <mark>IG 的頁面會隨著螢幕寬度更改而重新排版</mark>，Xpath 的路徑也會跟著改變
<ul>
<li>
<p><strong>寬螢幕</strong> Xpath</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-08.webp" width="640" height="94" alt="Imgur" loading="lazy" decoding="async"></p>
<pre tabindex="0"><code>//*[@id=&#34;react-root&#34;]/section/main/div/header/section/ul/li[2]/a/span
</code></pre></li>
<li>
<p><strong>窄螢幕</strong> Xpath</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-09.webp" width="640" height="71" alt="Imgur" loading="lazy" decoding="async"></p>
<pre tabindex="0"><code>//*[@id=&#34;react-root&#34;]/section/main/div/ul/li[2]/a/span
</code></pre></li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="32-設定瀏覽器開啟時的視窗大小">3.2 設定瀏覽器開啟時的視窗大小<a class="h-anchor" href="#32-%e8%a8%ad%e5%ae%9a%e7%80%8f%e8%a6%bd%e5%99%a8%e9%96%8b%e5%95%9f%e6%99%82%e7%9a%84%e8%a6%96%e7%aa%97%e5%a4%a7%e5%b0%8f" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>為了避免因為螢幕寬度不一樣造成 Xpath 不同，所以我們要<mark>設定瀏覽器開啟時的視窗大小</mark>(本專案以寬螢幕作為範例)，這樣才能保證你的爬蟲在不同電腦有一樣的執行結果</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 建立browser的類型
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).withCapabilities(options).build();
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//考慮到ig在不同螢幕寬度時的Xpath不一樣，所以我們要在這裡設定統一的視窗大小
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.manage().window().setRect({ width<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1280</span>, height<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">800</span>, x<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">0</span>, y<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">0</span> });
</span></span></code></pre></div></li>
</ul>
<hr>
<h3 class="h-nomark" id="33-完成前往-ig-帳號並取得追蹤人數的程式">3.3 完成前往 IG 帳號並取得追蹤人數的程式<a class="h-anchor" href="#33-%e5%ae%8c%e6%88%90%e5%89%8d%e5%be%80-ig-%e5%b8%b3%e8%99%9f%e4%b8%a6%e5%8f%96%e5%be%97%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8%e7%9a%84%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>在固定螢幕寬度後，使用 Xpath 就能穩定的抓出帳號追蹤人數</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//登入成功後要前往粉專頁面
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fanpage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://www.instagram.com/baobaonevertell/&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.get(fanpage)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.sleep(<span style="color:#a5d6ff">3000</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> ig_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;<span style="color:#8b949e;font-style:italic">//這是紀錄IG追蹤人數
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> ig_trace_xpath <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">`//*[@id=&#34;react-root&#34;]/section/main/div/header/section/ul/li[2]/a/span`</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> ig_trace_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(ig_trace_xpath)))
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// ig因為當人數破萬時文字不會顯示，所以改抓title
</span></span></span><span style="display:flex;"><span>ig_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> ig_trace_ele.getAttribute(<span style="color:#a5d6ff">&#39;title&#39;</span>)
</span></span><span style="display:flex;"><span>console.log(<span style="color:#a5d6ff">`追蹤人數：</span><span style="color:#a5d6ff">${</span>ig_trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>driver.quit();
</span></span></code></pre></div></li>
</ul>
<hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
<li>
<p>你會看到 Instagram 自動登入 → 跳轉到指定帳號 → 關閉</p>
</li>
<li>
<p>確認輸出正確的粉專追蹤人數你就成功嚕～</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-10.webp" width="1024" height="133" alt="執行程式（圖 10）" loading="lazy" decoding="async"></p>
</li>
</ol>
<p>到目前為止我們已經可以抓出 FB &amp; IG 粉專的追蹤人數了，充滿好奇心的讀者可以先試著看看利用爬蟲爬完 FB 粉專後繼續爬 IG</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day11">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天FB爬蟲的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day10_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天的功能喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整你.env檔填上IG登入資訊</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://stackoverflow.com/a/49940401">Filling in login forms in Instagram using selenium and webdriver (chrome) python OSX</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-11/images/fb-ig-crawler-day-11-11.webp" width="1024" height="233" alt="參考資源（圖 11）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day10】關閉干擾爬蟲的彈窗，將FB粉專追蹤數納入囊中</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-10/</link>
      <pubDate>Fri, 25 Sep 2020 09:09:47 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-10/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>我的文章是以專案的角度寫作，遇到問題才會分析解決的方案，與專門介紹套件的文章風格不同，因為這個系列文撰寫的初衷並不是教一個技術而是培養你解決問題的能力</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/cover_hu_4c30013eb6506aeb.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>遇到困難挫折時，多想一下自己的初衷；如果你沒有初衷，那就想一下薪水吧</p>
</blockquote>
<h2 id="筆者有話先說">筆者有話先說<a class="h-anchor" href="#%e7%ad%86%e8%80%85%e6%9c%89%e8%a9%b1%e5%85%88%e8%aa%aa" aria-label="這個段落的連結"></a></h2>
<p>我的文章是以專案的角度寫作，遇到問題才會分析解決的方案，與專門介紹套件的文章風格不同，因為這個系列文撰寫的初衷並不是教一個技術而是培養你解決問題的能力</p>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-關閉通知彈窗">1. 關閉通知彈窗<a class="h-anchor" href="#1-%e9%97%9c%e9%96%89%e9%80%9a%e7%9f%a5%e5%bd%88%e7%aa%97" aria-label="這個段落的連結"></a></h3>
<p>1.1 用 Google 關鍵字來尋找解答</p>
<p>1.2 調整 chrome 的參數來關閉通知彈窗</p>
<h3 class="h-nomark" id="2-用參數區分爬蟲要跑-fb-的經典版還是新版">2. 用參數區分爬蟲要跑 FB 的經典版還是新版<a class="h-anchor" href="#2-%e7%94%a8%e5%8f%83%e6%95%b8%e5%8d%80%e5%88%86%e7%88%ac%e8%9f%b2%e8%a6%81%e8%b7%91-fb-%e7%9a%84%e7%b6%93%e5%85%b8%e7%89%88%e9%82%84%e6%98%af%e6%96%b0%e7%89%88" aria-label="這個段落的連結"></a></h3>
<p>2.1 判斷自己的 FB 是經典版本還是新版</p>
<p>2.2 在 .env加入參數來做區別</p>
<h3 class="h-nomark" id="3-分析今日目標前往-fb-粉專取得追蹤人數資訊">3. 分析今日目標：前往 FB 粉專取得追蹤人數資訊<a class="h-anchor" href="#3-%e5%88%86%e6%9e%90%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99%e5%89%8d%e5%be%80-fb-%e7%b2%89%e5%b0%88%e5%8f%96%e5%be%97%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8%e8%b3%87%e8%a8%8a" aria-label="這個段落的連結"></a></h3>
<h3 class="h-nomark" id="4-前往-fb-粉專">4. 前往 FB 粉專<a class="h-anchor" href="#4-%e5%89%8d%e5%be%80-fb-%e7%b2%89%e5%b0%88" aria-label="這個段落的連結"></a></h3>
<p>4.1 程式執行的順序跟你想的不一樣</p>
<p>4.2 從畫面判斷使用者是否登入成功，使用 Class 抓取網頁元件</p>
<p>4.3 加入登入完成才能前往粉專的邏輯</p>
<h3 class="h-nomark" id="5-取得-fb-粉專追蹤人數">5. 取得 FB 粉專追蹤人數<a class="h-anchor" href="#5-%e5%8f%96%e5%be%97-fb-%e7%b2%89%e5%b0%88%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8" aria-label="這個段落的連結"></a></h3>
<p>5.1 在粉專頁面找出追蹤人數的位置，並分析取出元件的方法</p>
<p>5.2 取出粉專的追蹤者人數</p>
<h3 class="h-nomark" id="6-完成爬蟲後關閉瀏覽器">6. 完成爬蟲後關閉瀏覽器<a class="h-anchor" href="#6-%e5%ae%8c%e6%88%90%e7%88%ac%e8%9f%b2%e5%be%8c%e9%97%9c%e9%96%89%e7%80%8f%e8%a6%bd%e5%99%a8" aria-label="這個段落的連結"></a></h3>
<hr>
<h2 class="h-nomark" id="1-關閉通知彈窗-1">1. 關閉通知彈窗<a class="h-anchor" href="#1-%e9%97%9c%e9%96%89%e9%80%9a%e7%9f%a5%e5%bd%88%e7%aa%97-1" aria-label="這個段落的連結"></a></h2>
<p>昨天登入FB的時候應該大家畫面都會長這樣</p>
<ul>
<li>
<p>經典版：</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-01.webp" width="1024" height="386" alt="1. 關閉通知彈窗（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
<li>
<p>新版：</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-02.webp" width="1024" height="396" alt="1. 關閉通知彈窗（圖 2）" loading="lazy" decoding="async"></p>
</li>
</ul>
<p><mark>在通知彈窗存在的狀態下是無法抓取網頁元件的</mark>(充滿嘗試精神的讀者的可以嘗試看看)</p>
<h3 class="h-nomark" id="11-用-google-關鍵字來尋找解答">1.1 用 Google 關鍵字來尋找解答<a class="h-anchor" href="#11-%e7%94%a8-google-%e9%97%9c%e9%8d%b5%e5%ad%97%e4%be%86%e5%b0%8b%e6%89%be%e8%a7%a3%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p><mark>遇到你覺得很多人也會遇到的問題時</mark>用 Google 找解答會比你研究文檔快很多，像這個問題在 Google 下關鍵字：<code>nodejs selenium-webdriver notifications</code>，前幾篇一定會有文章能解決你的問題</p>
<blockquote>
<p>在這裡 <strong>通知</strong> 的英文為 <strong>notification</strong>，<mark>再次提醒用英文比較容易找到解答</mark>，如果一開始不知道哪個才是準確的關鍵字，你可以先用 <strong>Google 翻譯</strong>，然後每個單字都嘗試看看</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="12-調整-chrome-的參數來關閉通知彈窗">1.2 調整 chrome 的參數來關閉通知彈窗<a class="h-anchor" href="#12-%e8%aa%bf%e6%95%b4-chrome-%e7%9a%84%e5%8f%83%e6%95%b8%e4%be%86%e9%97%9c%e9%96%89%e9%80%9a%e7%9f%a5%e5%bd%88%e7%aa%97" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>更改 chrome 瀏覽器 notifications 的設定</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> chrome <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver/chrome&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> options <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.Options();
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//因為FB會有notifications干擾到爬蟲，所以要先把它關閉
</span></span></span><span style="display:flex;"><span>options.setUserPreferences({ <span style="color:#a5d6ff">&#39;profile.default_content_setting_values.notifications&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1</span> });
</span></span></code></pre></div></li>
<li>
<p>讓 chrome 瀏覽器依照這個設定建立</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).
</span></span><span style="display:flex;"><span>    withCapabilities(options).build();<span style="color:#8b949e;font-style:italic">// 建立這個browser的類型
</span></span></span></code></pre></div></li>
<li>
<p><strong>index.js</strong> 加入上面的設定後在終端機(Terminal)執行指令就<strong>會發現彈窗提示不見了</strong>!</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
</ol>
<hr>
<h2 class="h-nomark" id="2-用參數區分爬蟲要跑-fb-的經典版還是新版-1">2. 用參數區分爬蟲要跑 FB 的經典版還是新版<a class="h-anchor" href="#2-%e7%94%a8%e5%8f%83%e6%95%b8%e5%8d%80%e5%88%86%e7%88%ac%e8%9f%b2%e8%a6%81%e8%b7%91-fb-%e7%9a%84%e7%b6%93%e5%85%b8%e7%89%88%e9%82%84%e6%98%af%e6%96%b0%e7%89%88-1" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-判斷自己的-fb-是經典版本還是新版">2.1 判斷自己的 FB 是經典版本還是新版<a class="h-anchor" href="#21-%e5%88%a4%e6%96%b7%e8%87%aa%e5%b7%b1%e7%9a%84-fb-%e6%98%af%e7%b6%93%e5%85%b8%e7%89%88%e6%9c%ac%e9%82%84%e6%98%af%e6%96%b0%e7%89%88" aria-label="這個段落的連結"></a></h3>
<p>FB 網頁更新非常頻繁，到現在更有<mark>經典版以及新版的區別</mark>，如果不清楚自己是什麼版本可以看下面的畫面做判斷</p>
<ul>
<li>
<p><strong>經典版</strong></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-03.webp" width="1024" height="359" alt="2.1 判斷自己的 FB 是經典版本還是新版（圖 3）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>新版</strong></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-04.webp" width="1024" height="424" alt="2.1 判斷自己的 FB 是經典版本還是新版（圖 4）" loading="lazy" decoding="async"></p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-在-env加入參數來做區別">2.2 在 .env加入參數來做區別<a class="h-anchor" href="#22-%e5%9c%a8-env%e5%8a%a0%e5%85%a5%e5%8f%83%e6%95%b8%e4%be%86%e5%81%9a%e5%8d%80%e5%88%a5" aria-label="這個段落的連結"></a></h3>
<p>因為有經典版(classic)及新版(new)的區別，所以我們在 .env環境設定檔需要增加一個參數：<code>FB_VERSION</code> 來識別，檔案調整如下：</p>
<h4 id="env">.env<a class="h-anchor" href="#env" aria-label="這個段落的連結"></a></h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#填寫自己登入FB的真實資訊(建議開小帳號來實驗，因為帳號使用太頻繁會被官方鎖住)</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">FB_USERNAME</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;fb username&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">FB_PASSWORD</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;fb password&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#FB跑經典版還是新版(classic/new)</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">FB_VERSION</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;new&#39;</span>
</span></span></code></pre></div><blockquote>
<p>接下來的畫面也會依據經典版以及新版做相關的說明</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="3-分析今日目標前往-fb-粉專取得追蹤人數資訊-1">3. 分析今日目標：前往 FB 粉專取得追蹤人數資訊<a class="h-anchor" href="#3-%e5%88%86%e6%9e%90%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99%e5%89%8d%e5%be%80-fb-%e7%b2%89%e5%b0%88%e5%8f%96%e5%be%97%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8%e8%b3%87%e8%a8%8a-1" aria-label="這個段落的連結"></a></h2>
<p>面對複雜問題時，我一律建議把<mark>大問題先拆解成小問題，小問題再細分成功能項目</mark>，這樣同一時間只需集中注意力完成一個功能，這樣的做法能帶給你階段性成就感，也讓你大腦有喘息的空間</p>
<p>像是今天要做的功能 <strong>前往 FB 粉專取得追蹤人數資訊</strong> 可以拆分成幾個步驟：</p>
<ol>
<li>前往FB粉專</li>
<li>找出追蹤者人數的元件位置</li>
<li>關閉瀏覽器</li>
</ol>
<blockquote>
<p>建議大家可以自己先按照昨天所提供的方法來實做看看會遇到什麼樣的問題，再來看下面我我所遇到的狀況及解決方式</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="4-前往-fb-粉專-1">4. 前往 FB 粉專<a class="h-anchor" href="#4-%e5%89%8d%e5%be%80-fb-%e7%b2%89%e5%b0%88-1" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="41-程式執行的順序跟你想的不一樣">4.1 程式執行的順序跟你想的不一樣<a class="h-anchor" href="#41-%e7%a8%8b%e5%bc%8f%e5%9f%b7%e8%a1%8c%e7%9a%84%e9%a0%86%e5%ba%8f%e8%b7%9f%e4%bd%a0%e6%83%b3%e7%9a%84%e4%b8%8d%e4%b8%80%e6%a8%a3" aria-label="這個段落的連結"></a></h3>
<p>我一開始天真的以為在使用者登入後兩行程式碼就能解決</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//登入成功後要前往粉專頁面
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fanpage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://www.facebook.com/baobaonevertell/&#34;</span> 
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.get(fanpage)
</span></span></code></pre></div><p>但實際執行後你會發現很詭異的事情，<mark>在FB登入成功前網頁就直接導向到粉絲專頁了</mark></p>
<hr>
<h3 class="h-nomark" id="42-從畫面判斷使用者是否登入成功使用-class-抓取網頁元件">4.2 從畫面判斷使用者是否登入成功，使用 Class 抓取網頁元件<a class="h-anchor" href="#42-%e5%be%9e%e7%95%ab%e9%9d%a2%e5%88%a4%e6%96%b7%e4%bd%bf%e7%94%a8%e8%80%85%e6%98%af%e5%90%a6%e7%99%bb%e5%85%a5%e6%88%90%e5%8a%9f%e4%bd%bf%e7%94%a8-class-%e6%8a%93%e5%8f%96%e7%b6%b2%e9%a0%81%e5%85%83%e4%bb%b6" aria-label="這個段落的連結"></a></h3>
<p>因為 FB 在執行登入作業時需要<strong>等待 server 回應資料確認使用者身份</strong>，這塊我們可以從 <mark>FB 有什麼元件是登入後才會出現在畫面</mark>的這個方向去思考，接下來我會介紹<mark>用 Class 抓取網頁元件</mark>的方法</p>
<ul>
<li>
<p><strong>經典版</strong>：一定要在登入後 Facebook 才會有名字顯示，class 為：<code>_1vp5</code></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-05.webp" width="1024" height="301" alt="4.2 從畫面判斷使用者是否登入成功，使用 Class 抓取網頁元件（圖 5）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>新版</strong>：一定要在登入後 Facebook 才會有訊息及通知區塊顯示，共同的 class 為：<code>fzdkajry</code></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-06.webp" width="1024" height="208" alt="4.2 從畫面判斷使用者是否登入成功，使用 Class 抓取網頁元件（圖 6）" loading="lazy" decoding="async"></p>
</li>
</ul>
<blockquote>
<p>昨天教的用 Xpath 取出元件的方法在這裡不適用，因為他們取出的路徑太過複雜，只要 FB 的版面一改版就有很高的機率壞掉<br>* 經典版：//<em>[@id=&ldquo;u_0_e&rdquo;]/div[1]/div[1]/div/a/span/span<br>* 新版： //</em>[@id=&ldquo;mount_0_0&rdquo;]/div/div[1]/div[1]/div[2]/div[4]/div[1]/div[1]/span/div/div[1]/svg</p>
</blockquote>
<ul>
<li>
<p><strong>為了方便區分經典版/新版的 FB</strong>，我們增加一個函式：<code>getCrawlerPath</code>，來回傳各自的爬蟲路徑</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> getCrawlerPath () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (process.env.FB_VERSION <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">&#39;new&#39;</span>) {<span style="color:#8b949e;font-style:italic">//如果是新版FB
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;fzdkajry&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {<span style="color:#8b949e;font-style:italic">//如果為設定皆默認為舊版
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;_1vp5&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
</ul>
<hr>
<h3 class="h-nomark" id="43-加入登入完成才能前往粉專的邏輯">4.3 加入登入完成才能前往粉專的邏輯<a class="h-anchor" href="#43-%e5%8a%a0%e5%85%a5%e7%99%bb%e5%85%a5%e5%ae%8c%e6%88%90%e6%89%8d%e8%83%bd%e5%89%8d%e5%be%80%e7%b2%89%e5%b0%88%e7%9a%84%e9%82%8f%e8%bc%af" aria-label="這個段落的連結"></a></h3>
<ul>
<li>我們用上一步的class：<code>fb_head_path</code>，作為判斷登入與否的依據</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// FB有經典版以及新版的區分，兩者的爬蟲路徑不同，我們藉由函式取得各自的路徑
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { fb_head_path } <span style="color:#ff7b72;font-weight:bold">=</span> getCrawlerPath();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//因為登入這件事情要等server回應，你直接跳轉粉絲專頁會導致登入失敗
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//以登入後才會出現的元件作為判斷登入與否的依據
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(fb_head_path)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//登入成功後要前往粉專頁面
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fanpage <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://www.facebook.com/baobaonevertell/&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.get(fanpage)
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span></code></pre></div><hr>
<h2 class="h-nomark" id="5-取得-fb-粉專追蹤人數-1">5. 取得 FB 粉專追蹤人數<a class="h-anchor" href="#5-%e5%8f%96%e5%be%97-fb-%e7%b2%89%e5%b0%88%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8-1" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="51-在粉專頁面找出追蹤人數的位置並分析取出元件的方法">5.1 在粉專頁面找出追蹤人數的位置，並分析取出元件的方法<a class="h-anchor" href="#51-%e5%9c%a8%e7%b2%89%e5%b0%88%e9%a0%81%e9%9d%a2%e6%89%be%e5%87%ba%e8%bf%bd%e8%b9%a4%e4%ba%ba%e6%95%b8%e7%9a%84%e4%bd%8d%e7%bd%ae%e4%b8%a6%e5%88%86%e6%9e%90%e5%8f%96%e5%87%ba%e5%85%83%e4%bb%b6%e7%9a%84%e6%96%b9%e6%b3%95" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>經典版</strong></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-07.webp" width="1024" height="803" alt="5.1 在粉專頁面找出追蹤人數的位置，並分析取出元件的方法（圖 7）" loading="lazy" decoding="async"></p>
<ul>
<li>
<p>紅框位置 Xpath 的路徑</p>
<pre tabindex="0"><code>//*[@id=&#34;PagesProfileHomeSecondaryColumnPagelet&#34;]/div/div[1]/div/div[1]/div[4]/div/div[2]/div
</code></pre></li>
<li>
<p>如果你只要爬這個粉絲團的話用這個 Xpath 就足夠了，但你如果常逛粉絲團，你會發現<mark>不是每個粉絲團顯示追蹤人數的 Xpath 位置都一樣</mark>，下面的粉絲團網址你可以點進去試試看：</p>
<ul>
<li><a href="https://www.facebook.com/missunexpected2015/">小姐非常有事</a></li>
</ul>
<pre tabindex="0"><code>//*[@id=&#34;PagesProfileHomeSecondaryColumnPagelet&#34;]/div/div[1]/div/div[2]/div[4]/div/div[2]/div
</code></pre><ul>
<li><a href="https://www.facebook.com/HumanDesignAu/">人類圖澳洲</a></li>
</ul>
<pre tabindex="0"><code>//*[@id=&#34;PagesProfileHomeSecondaryColumnPagelet&#34;]/div/div[3]/div/div[2]/div[4]/div/div[2]/div
</code></pre><p>仔細一看就會發現 <strong>每個 Xpath 都會有細微的不同</strong> ，所以昨天教的 Xpath 在這裡就失靈了，我們需要換一個方法來判斷，也就是該元件的 class 結構</p>
</li>
<li>
<p><strong>使用 class 找出網頁元件</strong></p>
<ul>
<li>
<p>下面的幾張圖你可以觀察到這個追蹤者的資訊都在相同的 <strong>class=&quot;_4bl9&quot;</strong> 之下</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-08.webp" width="230" height="149" alt="Imgur" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-09.webp" width="230" height="149" alt="Imgur" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-10.webp" width="230" height="149" alt="Imgur" loading="lazy" decoding="async"></p>
</li>
<li>
<p>但是 Facebook 有很多的元件都使用到這個 class 所以我們需要<strong>把所有符合的 class 都抓下來，透過分析字串(xxx人在追蹤)來得到正確的資訊</strong></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>新版</strong></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-11.webp" width="1024" height="670" alt="5.1 在粉專頁面找出追蹤人數的位置，並分析取出元件的方法（圖 11）" loading="lazy" decoding="async"></p>
<ul>
<li>
<p>紅框位置Xpath的路徑</p>
<pre tabindex="0"><code>//*[@id=&#34;mount_0_0&#34;]/div/div[1]/div[1]/div[3]/div/div/div[1]/div[1]/div[4]/div[2]/div/div[1]/div[2]/div[1]/div/div/div/div[2]/div[4]/div/div/div/div[2]/div/div/span/span
</code></pre></li>
<li>
<p><strong>與經典版相同的地方</strong>：不是每個粉絲團的 Xpath 都一樣，所以我們需要改用 class 結構來分析</p>
</li>
<li>
<p><strong>class 結構分析</strong>：你會很崩潰的發現他的 class 有夠多&hellip;</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-12.webp" width="950" height="132" alt="5.1 在粉專頁面找出追蹤人數的位置，並分析取出元件的方法（圖 12）" loading="lazy" decoding="async"></p>
<ul>
<li><mark>在發現這些 class 都與很多 facebook 元件共用的狀態下，你要思考的是哪個元件被共用的次數是最少的</mark>，經由這樣的邏輯思考後，發現 <code>class=&quot;knvmm38d&quot;</code> 是裡面共用次數最少的</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>為了方便區分經典版/新版的FB</strong>，我們將上面的路徑新增到 <code>getCrawlerPath</code> 回傳參數中</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> getCrawlerPath () {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (process.env.FB_VERSION <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">&#39;new&#39;</span>) {<span style="color:#8b949e;font-style:italic">//如果是新版FB
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;fzdkajry&#34;)]`</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_trace_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;knvmm38d&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {<span style="color:#8b949e;font-style:italic">//如果為設定皆默認為舊版
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_head_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[contains(@class,&#34;_1vp5&#34;)]`</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;fb_trace_path&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">`//*[@id=&#34;PagesProfileHomeSecondaryColumnPagelet&#34;]//*[contains(@class,&#34;_4bl9&#34;)]`</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
</ul>
<hr>
<h3 class="h-nomark" id="52-取出粉專的追蹤者人數">5.2 取出粉專的追蹤者人數<a class="h-anchor" href="#52-%e5%8f%96%e5%87%ba%e7%b2%89%e5%b0%88%e7%9a%84%e8%bf%bd%e8%b9%a4%e8%80%85%e4%ba%ba%e6%95%b8" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>因為這次要<mark>抓取的資料要從複數個 class 裡面篩選出來</mark>，但在我們無法決定 class 載入的順序的情況下，偶而會出現抓不到追蹤人數的元件，為了保證元件載入的穩定性，讓<mark>瀏覽器前往粉專頁面3秒後再進行爬蟲</mark></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.sleep(<span style="color:#a5d6ff">3000</span>)
</span></span></code></pre></div></li>
<li>
<p>在確認需要分析的 class 之後，只要把對應的 class 全部取出來轉換成文字，<strong>用 for/of迴圈做字串分析</strong>就能找出需要的資訊</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">await</span> driver.sleep(<span style="color:#a5d6ff">3000</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> fb_trace <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;<span style="color:#8b949e;font-style:italic">//這是紀錄FB追蹤人數
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//因為考慮到登入之後每個粉專顯示追蹤人數的位置都不一樣，所以就採用全抓在分析
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_trace_eles <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementsLocated(By.xpath(fb_trace_path)))
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">const</span> fb_trace_ele <span style="color:#ff7b72">of</span> fb_trace_eles) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_text <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> fb_trace_ele.getText()
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (fb_text.includes(<span style="color:#a5d6ff">&#39;人在追蹤&#39;</span>)) {
</span></span><span style="display:flex;"><span>        fb_trace <span style="color:#ff7b72;font-weight:bold">=</span> fb_text
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>console.log(<span style="color:#a5d6ff">`追蹤人數：</span><span style="color:#a5d6ff">${</span>fb_trace<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>)
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span><span style="display:flex;"><span>.
</span></span></code></pre></div><blockquote>
<p>儘量不要在 forEach 中使用 aysnc/await，因為他還需要透過一個 callback 函式才能使用，邏輯表現不如 for/of 來的直觀</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="6-完成爬蟲後關閉瀏覽器-1">6. 完成爬蟲後關閉瀏覽器<a class="h-anchor" href="#6-%e5%ae%8c%e6%88%90%e7%88%ac%e8%9f%b2%e5%be%8c%e9%97%9c%e9%96%89%e7%80%8f%e8%a6%bd%e5%99%a8-1" aria-label="這個段落的連結"></a></h2>
<p>確認爬蟲都執行完畢後，用下面這行程式即可關閉瀏覽器</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>driver.quit();
</span></span></code></pre></div><hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div></li>
<li>
<p>你會看到 Facebook 自動登入 → 跳轉到粉絲頁 → 關閉</p>
</li>
<li>
<p>如果能正確輸出該粉專的追蹤人數你就成功嚕～</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-13.webp" width="1024" height="134" alt="執行程式（圖 13）" loading="lazy" decoding="async"></p>
</li>
</ol>
<p>相信到這裡大家都能成功地抓出粉專的追蹤者人數了，對這個爬蟲專案應該也充滿了信心了吧！</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day10">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day9_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天的功能喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整.env檔
<ul>
<li>填上FB登入資訊</li>
<li>填上FB版本(classic/new)</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop">Using async/await with a forEach loop</a></li>
<li><a href="https://www.jishuwen.com/d/2M0c/zh-tw">JavaScript 的 4 種陣列遍歷方法： for VS forEach() VS for/in VS for/of</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-10/images/fb-ig-crawler-day-10-14.webp" width="1024" height="233" alt="參考資源（圖 14）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day9】爬蟲第一步，FB先登入</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-09/</link>
      <pubDate>Thu, 24 Sep 2020 09:06:37 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-09/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>一定要自己實作過一遍! 一定要自己實作過一遍! 一定要自己實作過一遍! 因為很重要所以要說三遍，前面幾篇文章比較偏向概念，但今後的文章概念實作並重；因為FB 與 IG 改版的速度極快，我現在提供的方法在他們改版後可能就會失效，所以文章的每個步驟要自己去實作才能理解為什麼這樣做，同…</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/cover_hu_1f310c577d8689c5.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<h2 class="h-nomark" id="-在爬蟲前請注意這些事情避免違法">⚠️ 在爬蟲前請注意這些事情避免違法<a class="h-anchor" href="#-%e5%9c%a8%e7%88%ac%e8%9f%b2%e5%89%8d%e8%ab%8b%e6%b3%a8%e6%84%8f%e9%80%99%e4%ba%9b%e4%ba%8b%e6%83%85%e9%81%bf%e5%85%8d%e9%81%95%e6%b3%95" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>請勿拿來蒐集個人隱私</strong>
<ul>
<li>相片、個人資料</li>
</ul>
</li>
<li><strong>請勿高頻率訪問一個網站</strong>
<ul>
<li>高頻率訪問一個網站可能對導致對方伺服器癱瘓(DDOS)</li>
</ul>
</li>
<li><strong>請勿盜取相關智慧財產權相關的資料</strong>
<ul>
<li>社群上發表的文章、有人統整過的資訊</li>
</ul>
</li>
<li><strong>請勿蒐集網站後台資料</strong>
<ul>
<li>僅抓取前端網頁有顯示的公開信息，不要去抓後台的敏感資訊</li>
</ul>
</li>
<li><strong>請勿作為破解工具</strong>
<ul>
<li>嘗試各種帳號密碼試圖登入自己沒有權限的頁面</li>
</ul>
</li>
</ol>
<blockquote>
<p>這系列的教學文章是<mark>想讓大家省去如同機器人般的操作，把時間精力放在分析資料上面</mark></p>
</blockquote>
<hr>
<h2 id="今天的學習要特別注意什麼">今天的學習要特別注意什麼？<a class="h-anchor" href="#%e4%bb%8a%e5%a4%a9%e7%9a%84%e5%ad%b8%e7%bf%92%e8%a6%81%e7%89%b9%e5%88%a5%e6%b3%a8%e6%84%8f%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h2>
<p>一定要自己實作過一遍!</p>
<p>一定要自己實作過一遍!</p>
<p>一定要自己實作過一遍!</p>
<p>因為很重要所以要說三遍，前面幾篇文章比較偏向概念，但今後的文章概念實作並重；因為<mark>FB 與 IG 改版的速度極快</mark>，我現在提供的方法在他們改版後可能就會失效，所以文章的<mark>每個步驟要自己去實作</mark>才能理解為什麼這樣做，同時培養自己分析網頁結構的能力</p>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="1-學會分析網頁結構---以facebook登入頁為範例">1. 學會分析網頁結構 - 以Facebook登入頁為範例<a class="h-anchor" href="#1-%e5%ad%b8%e6%9c%83%e5%88%86%e6%9e%90%e7%b6%b2%e9%a0%81%e7%b5%90%e6%a7%8b---%e4%bb%a5facebook%e7%99%bb%e5%85%a5%e9%a0%81%e7%82%ba%e7%af%84%e4%be%8b" aria-label="這個段落的連結"></a></h3>
<p>1.1 拆解平常登入 FB 的步驟</p>
<p>1.2 將平常操作的位置轉化為程式可以讀懂的路徑(Xpath)</p>
<h3 class="h-nomark" id="2-打造自動登入facebook的機器人">2. 打造自動登入FaceBook的機器人<a class="h-anchor" href="#2-%e6%89%93%e9%80%a0%e8%87%aa%e5%8b%95%e7%99%bb%e5%85%a5facebook%e7%9a%84%e6%a9%9f%e5%99%a8%e4%ba%ba" aria-label="這個段落的連結"></a></h3>
<p>2.1 了解 selenium-webdriver 如何操作網頁元件</p>
<p>2.2 在 .env檔填上 FB 登入資訊，並從程式中讀取</p>
<p>2.3 讓爬蟲幫你完成平日登入的步驟</p>
<hr>
<h2 class="h-nomark" id="1-學會分析網頁結構--以facebook登入頁為範例">1. 學會分析網頁結構 — 以Facebook登入頁為範例<a class="h-anchor" href="#1-%e5%ad%b8%e6%9c%83%e5%88%86%e6%9e%90%e7%b6%b2%e9%a0%81%e7%b5%90%e6%a7%8b--%e4%bb%a5facebook%e7%99%bb%e5%85%a5%e9%a0%81%e7%82%ba%e7%af%84%e4%be%8b" aria-label="這個段落的連結"></a></h2>
<p>你可以想像 <strong>selenium-webdriver</strong> 這個套件就是讓機器人代替你的手來做操作，<mark>想要讓機器幫你操作，你需要告訴他明確的操作位置</mark>，接下來向大家介紹如何抓出這些位置</p>
<h3 class="h-nomark" id="11-拆解平常登入fb的步驟">1.1 拆解平常登入FB的步驟<a class="h-anchor" href="#11-%e6%8b%86%e8%a7%a3%e5%b9%b3%e5%b8%b8%e7%99%bb%e5%85%a5fb%e7%9a%84%e6%ad%a5%e9%a9%9f" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p>先用 chrome 無痕模式打開<a href="https://www.facebook.com/login">Facebook登入頁面</a></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-01.webp" width="300" height="212" alt="Imgur" fetchpriority="high" decoding="async"></p>
</li>
<li>
<p>打開後的 FB 的登入畫面如下</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-02.webp" width="1024" height="613" alt="1.1 拆解平常登入FB的步驟（圖 2）" loading="lazy" decoding="async"></p>
<blockquote>
<p><mark>剛發完文章發現 FB 登入畫面又變惹QQ，不過操作上都一樣請勿驚慌</mark></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-03.webp" width="1024" height="647" alt="1.1 拆解平常登入FB的步驟（圖 3）" loading="lazy" decoding="async"></p>
</blockquote>
</li>
<li>
<p>接下來把你<mark>平常登入FB的動作分幾個步驟</mark>，下一步要對這些操作到的元件進行結構分析：</p>
<ol>
<li>輸入電子郵件或電話</li>
<li>輸入密碼</li>
<li>按下登入按鈕</li>
</ol>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-將平常操作的位置轉化為程式可以讀懂的路徑xpath">1.2 將平常操作的位置轉化為程式可以讀懂的路徑(Xpath)<a class="h-anchor" href="#12-%e5%b0%87%e5%b9%b3%e5%b8%b8%e6%93%8d%e4%bd%9c%e7%9a%84%e4%bd%8d%e7%bd%ae%e8%bd%89%e5%8c%96%e7%82%ba%e7%a8%8b%e5%bc%8f%e5%8f%af%e4%bb%a5%e8%ae%80%e6%87%82%e7%9a%84%e8%b7%af%e5%be%91xpath" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>我們先用先紅框圈出登入會用到的元件位置</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-04.webp" width="1024" height="613" alt="1.2 將平常操作的位置轉化為程式可以讀懂的路徑(Xpath)（圖 4）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>接著對<strong>元件按下滑鼠右鍵 → 檢查</strong>，就會進入開發者的介面</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-05.webp" width="1024" height="396" alt="1.2 將平常操作的位置轉化為程式可以讀懂的路徑(Xpath)（圖 5）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>然後你就會看到一堆不友善的程式碼，這個時候別緊張，我們原則上不需要理解他們在寫什麼，我們只要知他<strong>在什麼位置就好</strong></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-06.webp" width="1024" height="613" alt="1.2 將平常操作的位置轉化為程式可以讀懂的路徑(Xpath)（圖 6）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>想知道他位置的方法也很簡單，對開發者頁面的那個<strong>程式碼按右鍵 → Copy → Copy Xpath</strong></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-07.webp" width="1024" height="382" alt="1.2 將平常操作的位置轉化為程式可以讀懂的路徑(Xpath)（圖 7）" loading="lazy" decoding="async"></p>
<p>你就會得到 <strong>電子郵件或電話</strong> 元件在這個頁面的位置</p>
<pre tabindex="0"><code>//*[@id=&#34;email&#34;]
</code></pre><p>重複上面的步驟你面可以取得 <strong>密碼</strong> 元件的位置</p>
<pre tabindex="0"><code>//*[@id=&#34;pass&#34;]
</code></pre><p>以及 <strong>登入按鈕</strong> 元件的位置</p>
<pre tabindex="0"><code>//*[@id=&#34;loginbutton&#34;]
</code></pre><p>取得這三個元件的 Xpath 後我們便可以明確告訴機器人該做什麼事嚕</p>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="2-打造自動登入facebook的機器人-1">2. 打造自動登入FaceBook的機器人<a class="h-anchor" href="#2-%e6%89%93%e9%80%a0%e8%87%aa%e5%8b%95%e7%99%bb%e5%85%a5facebook%e7%9a%84%e6%a9%9f%e5%99%a8%e4%ba%ba-1" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-了解-selenium-webdriver-如何操作網頁元件">2.1 了解 selenium-webdriver 如何操作網頁元件<a class="h-anchor" href="#21-%e4%ba%86%e8%a7%a3-selenium-webdriver-%e5%a6%82%e4%bd%95%e6%93%8d%e4%bd%9c%e7%b6%b2%e9%a0%81%e5%85%83%e4%bb%b6" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>透過網頁的 Xpath 取出元件</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 取出想操作的元件 = 瀏覽器會等待直到路徑(//*[@id=&#34;email&#34;])的元件顯示才回傳該元件
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_email_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;email&#34;]`</span>)));
</span></span></code></pre></div></li>
<li>
<p><strong>在輸入框中輸入指定文字</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 將你想要輸入的文字透過sendKeys塞入元件
</span></span></span><span style="display:flex;"><span>fb_email_ele.sendKeys(fb_username)
</span></span></code></pre></div></li>
<li>
<p><strong>點擊按鈕</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 將取出的元件使用click函式觸發點擊事件
</span></span></span><span style="display:flex;"><span>login_elem.click()
</span></span></code></pre></div></li>
</ul>
<hr>
<h3 class="h-nomark" id="22-在-env檔填上-fb-登入資訊並從程式中讀取">2.2 在 .env檔填上 FB 登入資訊，並從程式中讀取<a class="h-anchor" href="#22-%e5%9c%a8-env%e6%aa%94%e5%a1%ab%e4%b8%8a-fb-%e7%99%bb%e5%85%a5%e8%b3%87%e8%a8%8a%e4%b8%a6%e5%be%9e%e7%a8%8b%e5%bc%8f%e4%b8%ad%e8%ae%80%e5%8f%96" aria-label="這個段落的連結"></a></h3>
<p>理解關鍵程式的功能後我們來把他們組合起來吧：</p>
<ol>
<li>
<p>在 .env檔填寫 FB 登入的帳號密碼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#填寫自己登入FB的真實資訊(建議開小帳號來實驗，因為帳號使用太頻繁會被官方鎖住)</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">FB_USERNAME</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;fb username&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">FB_PASSWORD</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;fb password&#39;</span>
</span></span></code></pre></div></li>
<li>
<p>取出在 .env檔案裡面的 FB 帳號密碼提供主程式使用</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>require(<span style="color:#a5d6ff">&#39;dotenv&#39;</span>).config(); <span style="color:#8b949e;font-style:italic">//載入.env環境檔
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">//取出.env檔案填寫的FB資訊
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_username <span style="color:#ff7b72;font-weight:bold">=</span> process.env.FB_USERNAME
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fb_userpass <span style="color:#ff7b72;font-weight:bold">=</span> process.env.FB_PASSWORD
</span></span></code></pre></div></li>
</ol>
<hr>
<h3 class="h-nomark" id="23-讓爬蟲幫你完成平日登入的步驟">2.3 讓爬蟲幫你完成平日登入的步驟<a class="h-anchor" href="#23-%e8%ae%93%e7%88%ac%e8%9f%b2%e5%b9%ab%e4%bd%a0%e5%ae%8c%e6%88%90%e5%b9%b3%e6%97%a5%e7%99%bb%e5%85%a5%e7%9a%84%e6%ad%a5%e9%a9%9f" aria-label="這個段落的連結"></a></h3>
<ol>
<li>
<p>將套件中會使用到的函式引入</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> webdriver <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>), <span style="color:#8b949e;font-style:italic">// 加入虛擬網頁套件
</span></span></span><span style="display:flex;"><span>    By <span style="color:#ff7b72;font-weight:bold">=</span> webdriver.By,<span style="color:#8b949e;font-style:italic">//你想要透過什麼方式來抓取元件，通常使用xpath、css
</span></span></span><span style="display:flex;"><span>    until <span style="color:#ff7b72;font-weight:bold">=</span> webdriver.until;<span style="color:#8b949e;font-style:italic">//直到抓到元件才進入下一步(可設定等待時間)
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> chrome <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver/chrome&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> path <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;path&#39;</span>);<span style="color:#8b949e;font-style:italic">//用於處理文件路徑的小工具
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;fs&#34;</span>);<span style="color:#8b949e;font-style:italic">//讀取檔案用
</span></span></span></code></pre></div></li>
<li>
<p>將我們對網頁操作的操作加入程式</p>
<ul>
<li>前往 facebook 登入頁面 → 輸入登入資訊 → 按下登入按鈕</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> loginFacebook () {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>checkDriver()) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).build();
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> web <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;https://www.facebook.com/login&#39;</span>;<span style="color:#8b949e;font-style:italic">//我們要前往FB
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> driver.get(web)<span style="color:#8b949e;font-style:italic">//在這裡要用await確保打開完網頁後才能繼續動作
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//填入fb登入資訊
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//使用until是要求直到網頁顯示了這個元件才能執行下一步
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_email_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.
</span></span><span style="display:flex;"><span>        wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;email&#34;]`</span>)));<span style="color:#8b949e;font-style:italic">//找出填寫email的元件
</span></span></span><span style="display:flex;"><span>    fb_email_ele.sendKeys(fb_username)<span style="color:#8b949e;font-style:italic">//將使用者的資訊填入
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> fb_pass_ele <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.
</span></span><span style="display:flex;"><span>        wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;pass&#34;]`</span>)));
</span></span><span style="display:flex;"><span>    fb_pass_ele.sendKeys(fb_userpass)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">//抓到登入按鈕然後點擊
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> login_elem <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> driver.
</span></span><span style="display:flex;"><span>        wait(until.elementLocated(By.xpath(<span style="color:#a5d6ff">`//*[@id=&#34;loginbutton&#34;]`</span>)))
</span></span><span style="display:flex;"><span>    login_elem.click()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>loginFacebook()<span style="color:#8b949e;font-style:italic">//登入FB
</span></span></span></code></pre></div></li>
</ol>
<hr>
<h3 id="執行程式">執行程式<a class="h-anchor" href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<p>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div><blockquote>
<p>你會看到 chrome 的應用程式自動打開並且成功登入 Facebook</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-08.webp" width="1024" height="386" alt="執行程式（圖 8）" loading="lazy" decoding="async"></p>
</blockquote>
<p>如果模擬器讓你成功登入 FB 可以在下方留言讓我知道喔，登入成功的瞬間有沒有充滿成就感呢？</p>
<hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day9">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day8_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天的功能喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
<li>調整你.env檔填上FB登入資訊</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://blog.happycoding.today/python-crawler-analysis/">Python 爬蟲解析：以爬取臉書社團為案例，使用 Selenium 來進行網頁模擬爬蟲</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-09/images/fb-ig-crawler-day-09-09.webp" width="1024" height="233" alt="參考資源（圖 9）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>【Day8】selenium-webdriver：爬蟲起手式，帶你認識所見即所得的爬蟲工具</title>
      <link>https://deanlin.net/blog/202009/fb-ig-crawler-day-08/</link>
      <pubDate>Wed, 23 Sep 2020 13:45:10 +0800</pubDate>
      <guid>https://deanlin.net/blog/202009/fb-ig-crawler-day-08/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>在決定以爬蟲作為主題時有朋友勸我不要寫這類有爭議的主題，但因為以下幾點我還是選了這個主題：</description>
      <media:content url="https://deanlin.net/blog/202009/fb-ig-crawler-day-08/images/cover_hu_a9dd8b55adb3df72.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<h2 id="為什麼選爬蟲作為主題">為什麼選爬蟲作為主題?<a class="h-anchor" href="#%e7%82%ba%e4%bb%80%e9%ba%bc%e9%81%b8%e7%88%ac%e8%9f%b2%e4%bd%9c%e7%82%ba%e4%b8%bb%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<p>在決定以爬蟲作為主題時有朋友勸我不要寫這類有爭議的主題，但因為以下幾點我還是選了這個主題：</p>
<ol>
<li><strong>爬蟲道德觀</strong>：有些網路文章道德觀崩壞，把一個好的技術變成智慧財產的盜賊</li>
<li><strong>中文資源缺乏，技術過時</strong>：中文的教學文章較少，許多範例的技術到今天已經無法使用了</li>
<li><strong>缺乏系統性的整合</strong>：網路上的資源相對零碎，學習上缺乏系統性，容易在學習過程中遇到問題而半途而廢</li>
</ol>
<hr>
<h2 id="學習爬蟲對我有什麼好處">學習爬蟲對我有什麼好處?<a class="h-anchor" href="#%e5%ad%b8%e7%bf%92%e7%88%ac%e8%9f%b2%e5%b0%8d%e6%88%91%e6%9c%89%e4%bb%80%e9%ba%bc%e5%a5%bd%e8%99%95" aria-label="這個段落的連結"></a></h2>
<ul>
<li><strong>如果你是工程師</strong>
<ol>
<li>最直接的好處就是讓你在<mark>未來職涯上有更多的選擇</mark></li>
<li>可以大量<mark>訓練你對網頁架構的認知</mark></li>
<li>當你學會爬蟲時，你就能寫出相對來說<mark>反爬蟲的網頁</mark>了</li>
</ol>
</li>
<li><strong>如果你是行銷廣告、電商小編</strong>
<ol>
<li>你可以透過爬蟲來<mark>了解網路世界在資料這塊的邏輯</mark></li>
<li>就算你看完文章後還是無法完全掌握，但當你請工程師來撰寫爬蟲時，你能很具體地<mark>說出你的需求並與工程師溝通順暢</mark></li>
<li>我認為掌握<mark>分析資料能力</mark>的人才最有機會做出能說服公司的策略</li>
</ol>
</li>
</ul>
<hr>
<h2 id="今日目標">今日目標<a class="h-anchor" href="#%e4%bb%8a%e6%97%a5%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<ol>
<li>知道如何選擇適合的爬蟲工具</li>
<li>使用 selenium-webdriver 套件開啟瀏覽器：<code>openCrawlerWeb</code></li>
<li>解決 windows 無法自動讀取 chromedriver.exe 路徑的問題
<ul>
<li>寫判斷 Driver 是否設定的函式：<code>checkDriver</code></li>
</ul>
</li>
<li>將 <code>checkDriver</code> 整合進 <code>openCrawlerWeb</code></li>
</ol>
<hr>
<h2 class="h-nomark" id="1-如何選擇適合的爬蟲工具">1. 如何選擇適合的爬蟲工具?<a class="h-anchor" href="#1-%e5%a6%82%e4%bd%95%e9%81%b8%e6%93%87%e9%81%a9%e5%90%88%e7%9a%84%e7%88%ac%e8%9f%b2%e5%b7%a5%e5%85%b7" aria-label="這個段落的連結"></a></h2>
<p>在<a href="https://deanlin.net/blog/202009/fb-ig-crawler-day-05/">Day5</a>有提到尋找工具的方法，現在我們依序來說明:</p>
<ol>
<li><strong>官方資源</strong>
<ul>
<li>由於近年 FB &amp; IG 的 api 改版頻繁，加上各式各樣權限的限制導致我們連公開資料都無法得到，所以在這裡我們放棄使用官方提供的 api</li>
</ul>
</li>
<li><strong>網路資源</strong>
<ul>
<li>透過下關鍵字我們可以看到很多人分享爬蟲的經驗，我們把這些經驗中看起來<mark>能用的套件記錄下來</mark></li>
</ul>
</li>
<li><strong>npm 套件</strong>
<ul>
<li>先在 <a href="https://www.npmjs.com/">npm 官網</a> 確認這些套件健康程度，<strong>太少人用或太久沒更新的就淘汰掉</strong>，接著進入<mark>實際測試</mark>確認是否符合專案需求，以下是我當時找到的資源並附上實測後的分析:
<ol>
<li><strong>request &amp; cheerio</strong>
<ul>
<li>下關鍵字候第一個找到的資源，使用範例程式測試後確認他可以透過網址抓到網頁元素並進行分析</li>
<li>but&hellip;他在 FB、IG 面前毫無用武之地，因為 FB 會偵測你使用的瀏覽器，所以在第一關他就陣亡了</li>
</ul>
</li>
<li><strong>selenium-webdriver</strong>
<ul>
<li>基本上他是一個無論是什麼網頁都能夠爬蟲的工具了，我認為非常適合新手使用</li>
<li>工具運行時會開啟一個瀏覽器，這個瀏覽器會依據撰寫的腳本來模擬人類操作的每個步驟</li>
<li><mark>所見即所得，看得到的資訊都能夠抓下來，學習起來非常容易且直覺，所以這是本專案最後選擇的工具</mark></li>
</ul>
</li>
<li><strong>Puppeteer</strong>
<ul>
<li>操作邏輯與 selenium-webdriver 非常類似</li>
<li>預設附帶 Chronium 瀏覽器，不會有瀏覽器匹配問題(但是只能用這個瀏覽器QQ)</li>
<li>只能使用 Node.js 撰寫，不像 selenium-webdriver 有多種語言的 binding</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="2-使用-selenium-webdriver-套件開啟瀏覽器opencrawlerweb">2. 使用 selenium-webdriver 套件開啟瀏覽器：<code>openCrawlerWeb</code><a class="h-anchor" href="#2-%e4%bd%bf%e7%94%a8-selenium-webdriver-%e5%a5%97%e4%bb%b6%e9%96%8b%e5%95%9f%e7%80%8f%e8%a6%bd%e5%99%a8opencrawlerweb" aria-label="這個段落的連結"></a></h2>
<p>我的文章會會依據需求使用到它的各種功能，如果有迫不及待的小夥伴也可以先去<a href="https://www.selenium.dev/documentation/en/">官網</a>來更深刻的了解他</p>
<ol>
<li>
<p><strong><code>selenium-webdriver</code>前置環境安裝</strong></p>
<ul>
<li>
<p>先在終端機(Terminal)下指令安裝他</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn add selenium-webdriver
</span></span></code></pre></div></li>
<li>
<p>本專案使用的模擬器是 chrome，電腦還沒裝的請先<a href="https://www.google.com/intl/zh-TW/chrome/">下載</a></p>
</li>
<li>
<p>因為跑 selenium 需要用到 driver，大家可以依照自己的作業系統做設定</p>
<ul>
<li>mac 作業系統
<ul>
<li>如果你用的電腦是 mac，恭喜你，不要需要額外下載 chrome driver 就能夠直接寫使用(不過 chrome 還是要下載)</li>
</ul>
</li>
<li>windows 作業系統
<ol>
<li>
<p><strong>先確認自己 chrome 版本</strong>：將<code>chrome://settings/help</code>貼到你的網址列</p>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-08/images/fb-ig-crawler-day-08-01.webp" width="1024" height="537" alt="2. 使用 selenium-webdriver 套件開啟瀏覽器：openCrawlerWeb（圖 1）" fetchpriority="high" decoding="async"></p>
</blockquote>
</li>
<li>
<p>下載<mark>與你 chrome 版本相同</mark>的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a></p>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-08/images/fb-ig-crawler-day-08-02.webp" width="1024" height="614" alt="2. 使用 selenium-webdriver 套件開啟瀏覽器：openCrawlerWeb（圖 2）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-08/images/fb-ig-crawler-day-08-03.webp" width="1024" height="335" alt="2. 使用 selenium-webdriver 套件開啟瀏覽器：openCrawlerWeb（圖 3）" loading="lazy" decoding="async"></p>
</blockquote>
</li>
<li>
<p>下載的檔案解壓縮後將 <code>chromedriver.exe 放到專案根目錄下</code></p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>用<code>openCrawlerWeb</code>函式打開爬蟲用網頁</strong></p>
<ul>
<li>
<p>因為 selenium-webdriver 是使用非同步函式建立瀏覽器(browser)，所以我們需要加上 await 明確告訴程式必須建立完瀏覽器才能打開網頁</p>
<blockquote>
<p>PS. 因為 javascript 支援非同步語法，所以我們必須很明確地告訴程式他要執行的順序(<strong>在 async 的函式中用 await，是標明必須等待這項工作完成才能進入下一步</strong>)，否則他跑起來的順序跟你想的不一樣 <strong>(並非完成前面工作才執行下一步的順序)</strong>，想更深入理解的朋友可以參考<a href="https://ithelp.ithome.com.tw/articles/10194569">這篇文章</a>喔</p>
</blockquote>
</li>
</ul>
<h3 id="indexjs">index.js<a class="h-anchor" href="#indexjs" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>require(<span style="color:#a5d6ff">&#39;dotenv&#39;</span>).config(); <span style="color:#8b949e;font-style:italic">//載入.env環境檔
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> webdriver <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>) <span style="color:#8b949e;font-style:italic">// 加入虛擬網頁套件
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> openCrawlerWeb() {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 建立這個browser的類型
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).build();
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> web <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;https://www.google.com/&#39;</span>;<span style="color:#8b949e;font-style:italic">//填寫你想要前往的網站
</span></span></span><span style="display:flex;"><span>    driver.get(web)<span style="color:#8b949e;font-style:italic">//透國這個driver打開網頁
</span></span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>openCrawlerWeb()<span style="color:#8b949e;font-style:italic">//打開爬蟲網頁
</span></span></span></code></pre></div></li>
<li>
<p><strong>執行程式</strong><br>在專案資料夾的終端機(Terminal)執行指令</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn start
</span></span></code></pre></div><blockquote>
<p>如果執行順利，你會看到 chrome 應用程式自動打開並且進入 google 的首頁</p>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-08/images/fb-ig-crawler-day-08-04.webp" width="1024" height="803" alt="index.js（圖 4）" loading="lazy" decoding="async"></p>
</blockquote>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="3-解決-windows-無法自動讀取-chromedriverexe-路徑">3. 解決 windows 無法自動讀取 chromedriver.exe 路徑<a class="h-anchor" href="#3-%e8%a7%a3%e6%b1%ba-windows-%e7%84%a1%e6%b3%95%e8%87%aa%e5%8b%95%e8%ae%80%e5%8f%96-chromedriverexe-%e8%b7%af%e5%be%91" aria-label="這個段落的連結"></a></h2>
<p>因為有人回報部分 windows 就算把 chromedriver.exe 放在專案根目錄也讀不到，所以特別寫了一個 <code>checkDriver</code> 的函式，邏輯如下：</p>
<ul>
<li><strong>判斷電腦是否有預設的 chromedriver</strong>
<ul>
<li>Yes → 無須設定</li>
<li>No → 確認路徑下是否有 <code>chromedriver.exe</code> 的檔案
<ul>
<li>Yes → 設定 driver 路徑</li>
<li>No → 無法設定 driver 路徑回傳 false</li>
</ul>
</li>
</ul>
</li>
<li>備註
<ul>
<li><strong>try-catch</strong>：顧名思義就是先 try，如果發生問題就會 catch 並執行錯誤處理；之後會有文章深入討論 try-catch 對專案的重要性</li>
<li><strong>__dirname</strong>：目前檔案所在的資料夾路徑</li>
</ul>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> chrome <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver/chrome&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> path <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;path&#39;</span>);<span style="color:#8b949e;font-style:italic">//用於處理文件路徑的小工具
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;fs&#34;</span>);<span style="color:#8b949e;font-style:italic">//讀取檔案用
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> checkDriver() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        chrome.getDefaultService()<span style="color:#8b949e;font-style:italic">//確認是否有預設
</span></span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> {
</span></span><span style="display:flex;"><span>        console.warn(<span style="color:#a5d6ff">&#39;找不到預設driver!&#39;</span>);
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//&#39;../chromedriver.exe&#39;記得調整成自己的路徑
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> file_path <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;../chromedriver.exe&#39;</span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//請確認印出來日誌中的位置是否與你路徑相同
</span></span></span><span style="display:flex;"><span>        console.log(path.join(__dirname, file_path));
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//確認路徑下chromedriver.exe是否存在            
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (fs.existsSync(path.join(__dirname, file_path))) {
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic">//設定driver路徑
</span></span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">const</span> service <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.ServiceBuilder(path.join(__dirname, file_path)).build();
</span></span><span style="display:flex;"><span>            chrome.setDefaultService(service);
</span></span><span style="display:flex;"><span>            console.log(<span style="color:#a5d6ff">&#39;設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>            console.error(<span style="color:#a5d6ff">&#39;無法設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li>
<p><code>將 chromedriver.exe 放到根目錄後記得在 .gitignore 把它加進去忽略清單喔</code>，他屬於不需要版控的檔案</p>
<h3 id="gitignore">.gitignore<a class="h-anchor" href="#gitignore" aria-label="這個段落的連結"></a></h3>
<pre tabindex="0"><code>node_modules
.env
chromedriver.exe
</code></pre></li>
</ul>
<hr>
<h2 class="h-nomark" id="4-將checkdriver整合到opencrawlerweb">4. 將<code>checkDriver</code>整合到<code>openCrawlerWeb</code><a class="h-anchor" href="#4-%e5%b0%87checkdriver%e6%95%b4%e5%90%88%e5%88%b0opencrawlerweb" aria-label="這個段落的連結"></a></h2>
<p>在建立瀏覽器之前，我們先用 <code>checkDriver</code> 函式來確認 Driver 是否是設定，如果無法設定會跳出錯誤提醒，統整後程式如下</p>
<h3 id="indexjs-1">index.js<a class="h-anchor" href="#indexjs-1" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>require(<span style="color:#a5d6ff">&#39;dotenv&#39;</span>).config(); <span style="color:#8b949e;font-style:italic">//載入.env環境檔
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> webdriver <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver&#39;</span>) <span style="color:#8b949e;font-style:italic">// 加入虛擬網頁套件
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> chrome <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;selenium-webdriver/chrome&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> path <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;path&#39;</span>);<span style="color:#8b949e;font-style:italic">//用於處理文件路徑的小工具
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;fs&#34;</span>);<span style="color:#8b949e;font-style:italic">//讀取檔案用
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> checkDriver() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>        chrome.getDefaultService()<span style="color:#8b949e;font-style:italic">//確認是否有預設
</span></span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">catch</span> {
</span></span><span style="display:flex;"><span>        console.warn(<span style="color:#a5d6ff">&#39;找不到預設driver!&#39;</span>);
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//&#39;../chromedriver.exe&#39;記得調整成自己的路徑
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> file_path <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;../chromedriver.exe&#39;</span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//請確認印出來日誌中的位置是否與你路徑相同
</span></span></span><span style="display:flex;"><span>        console.log(path.join(__dirname, file_path));
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">//確認路徑下chromedriver.exe是否存在            
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (fs.existsSync(path.join(__dirname, file_path))) {
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic">//設定driver路徑
</span></span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">const</span> service <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> chrome.ServiceBuilder(path.join(__dirname, file_path)).build();
</span></span><span style="display:flex;"><span>            chrome.setDefaultService(service);
</span></span><span style="display:flex;"><span>            console.log(<span style="color:#a5d6ff">&#39;設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>            console.error(<span style="color:#a5d6ff">&#39;無法設定driver路徑&#39;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> openCrawlerWeb() {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>checkDriver()) {<span style="color:#8b949e;font-style:italic">// 檢查Driver是否是設定，如果無法設定就結束程式
</span></span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 建立這個broswer的類型
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> driver <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> <span style="color:#ff7b72">new</span> webdriver.Builder().forBrowser(<span style="color:#a5d6ff">&#34;chrome&#34;</span>).build();
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> web <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;https://www.google.com/&#39;</span>;<span style="color:#8b949e;font-style:italic">//填寫你想要前往的網站
</span></span></span><span style="display:flex;"><span>    driver.get(web)<span style="color:#8b949e;font-style:italic">//透國這個driver打開網頁
</span></span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>openCrawlerWeb()<span style="color:#8b949e;font-style:italic">//打開爬蟲網頁
</span></span></span></code></pre></div><hr>
<h3 id="ℹ-專案原始碼">ℹ️ 專案原始碼<a class="h-anchor" href="#%e2%84%b9-%e5%b0%88%e6%a1%88%e5%8e%9f%e5%a7%8b%e7%a2%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>今天的完整程式碼可以在<a href="https://github.com/dean9703111/ithelp_30days/tree/master/day8">這裡</a>找到喔</li>
<li>我也貼心地把昨天的把昨天的程式碼打包成<a href="https://github.com/dean9703111/ithelp_30days/raw/master/sampleCode/day7_sample_code.zip">壓縮檔</a>，你可以用裡面乾淨的環境來實作今天的功能喔
<ul>
<li>請記得在終端機下指令 <strong>yarn</strong> 才會把之前的套件安裝</li>
<li>windows需下載與你chrome版本相同的<a href="http://chromedriver.storage.googleapis.com/index.html">chrome driver</a>放在專案根目錄</li>
</ul>
</li>
</ul>
<hr>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://pjchender.blogspot.com/2017/12/js-error-handling.html">[JS] 談談 JavaScript 中的錯誤處理 Error Handling</a></li>
<li><a href="https://michaelchen.tech/puppeteer/why-or-why-not-puppeteer/">為什麼用 (或不用) Puppeteer</a></li>
<li><a href="https://ithelp.ithome.com.tw/articles/10194569">重新認識 JavaScript: Day 26 同步與非同步</a></li>
<li><a href="https://wcc723.github.io/javascript/2017/12/30/javascript-async-await/">鐵人賽：JavaScript Await 與 Async</a></li>
</ol>
<blockquote>
<p><em>免責聲明:文章技術僅抓取公開數據作爲研究，任何組織和個人不得以此技術盜取他人智慧財產、造成網站損害，否則一切后果由該組織或個人承擔。作者不承擔任何法律及連帶責任！</em></p>
</blockquote>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202009/fb-ig-crawler-day-08/images/fb-ig-crawler-day-08-05.webp" width="1024" height="233" alt="參考資源（圖 5）" loading="lazy" decoding="async"></p>
<p><strong>在許多人的幫助下，本系列文章已出版成書，並添加了新的篇章與細節補充：</strong></p>
<ul>
<li>加入更多實務經驗，用完整的開發流程讓讀者了解專案每個階段要注意的事項</li>
<li>將爬蟲的步驟與技巧做更詳細的說明，讓讀者可以輕鬆入門</li>
<li>調整專案架構
<ul>
<li>優化爬蟲程式，以更廣的視角來擷取網頁資訊</li>
<li>增加資料驗證、錯誤通知等功能，讓爬蟲執行遇到問題時可以第一時間通知使用者</li>
<li>排程部分改用 node-schedule &amp; pm2 的組合，讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊</li>
</ul>
</li>
</ul>
<p><strong>有興趣的朋友可以到天瓏書局選購，感謝大家的支持。</strong></p>
<p><strong>購書連結</strong>：<a href="https://www.tenlong.com.tw/products/9789864348008">https://www.tenlong.com.tw/products/9789864348008</a></p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
