<?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>Facebook｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/facebook/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>Facebook｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/facebook/</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>Sun, 27 Sep 2020 07:59:57 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/facebook/index.xml" rel="self" type="application/rss+xml" />
    <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>【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>
  </channel>
</rss>
