爬完富時中國 A50 網頁後快馬加鞭來爬 104 人力銀行的分頁搜尋結果, 此網站特點是分頁內容會隨著滑鼠滾輪向下捲動而持續載入, 模擬此動作需要用到 Selenium 來執行 Javascript. 此範例來自下面這本書 :
# 文科生也可以輕鬆學習網路爬蟲 : Python + Web Scraper (碁峰, 陳會安) 13-4
本系列之前的筆記參考 :
一. 檢視目標網頁 :
104 人力銀行首頁網址如下 :
首先用 Quick Javascript Switcher 檢查發現這個網站是 Javascript 動態生成的, 必須用 Selenium 來爬. 但與之前遇到的網站不同之處有二 :
- 一是此網站是使用 vue.js 前端技術寫的, 連輸入表單都是 Javascript 生成, 必須費一番手腳才能順利使用 Selenium 進行操控.
- 其二是當使用者搜尋關鍵字時不會一次列出全部結果, 而是以分頁的方式顯示部分結果, 當使用者向下滑動滾輪或拖曳卷軸時才動態載入下一頁的搜尋結果.
我們這次的任務很簡單, 就是搜尋 Python 相關工作後抓出前五頁工作機會的資訊.
在首頁左上方輸入框輸入 "Python" 後按右方 "搜尋" 鈕會出現一列列與 Python 相關的工作資訊, 當滑鼠往下拉時資料筆數就會不斷增加, 右方卷軸讓使用者感覺似乎沒有底部 (其實是有的), 這種網站採用的是無限捲動的分頁結構.
從下圖的搜尋結果可知, 搜尋關於 Python 的工作機會總共有 150 頁, 總頁數是放在一個關聯式陣列 initFilter 中的 totalPage 屬性裡 :
var initFilter = {"query":{"ro":0,"jobcat":"","isnew":"","kwop":7,"keyword":"Python",...
... (略) ...
"pageNo":1,"totalPage":150,"totalCount":7100,"personalBoost":0,"firstCustNo":""};
使用 Quick Javascript Switcher 檢查發現, 當 Javascript 功能關閉時, 卷軸就不再有無限捲動功能, 說明這確實是一個利用 Javascript 動態產生內容的網頁.
檢視搜尋關鍵字 "Python" 後的網頁原始碼會發現資料內容是放在一個型態為 JSON 的 Javascript 程式碼裡面, 它是透過 Javascript 繪製成網頁內容的 (網頁原始碼其實在此任務中用不到) :
<script type="application/ld+json">[
{
"@context": "https:\/\/schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "104 人力銀行",
"item": "https:\/\/www.104.com.tw"
},
{
"@type": "ListItem",
"position": 2,
"name": "找工作",
"item": "https:\/\/www.104.com.tw\/jobs\/search"
},
{
"@type": "ListItem",
"position": 3,
"name": "「Python」找工作職缺",
"item": "https:\/\/www.104.com.tw\/jobs\/search\/?keyword=Python"
}
]
},
{
"@context": "https:\/\/schema.org",
"@type": "WebSite",
"url": "\/\/www.104.com.tw",
"name": "104 人力銀行",
"potentialAction": {
"@type": "SearchAction",
"target": "https:\/\/www.104.com.tw\/jobs\/search\/?keyword={searchTerms}&jobsource=open",
"query-input": "required name=searchTerms"
}
},
{
"@context": "https:\/\/schema.org",
"@type": "Event",
"startDate": "2024\/06\/23",
"eventAttendanceMode": "https:\/\/schema.org\/MixedEventAttendanceMode",
"eventStatus": "EventScheduled",
"name": "Python Data Scientist",
"organizer": {
"@type": "Organization",
"name": "BigGo_樂方股份有限公司",
"url": "https:\/\/www.104.com.tw\/company\/1a2x6bkf3h"
},
"location": {
"@type": "Place",
"name": "高雄市鼓山區",
"address": {
"@type": "PostalAddress",
"postalCode": 804
}
},
"performer": {
"@type": "Organization",
"url": "https:\/\/www.104.com.tw\/company\/1a2x6bkf3h",
"name": "BigGo_樂方股份有限公司"
},
"description": "BigGo - [[[Python]]] 資料科學家\n\nBigGo是全球超過十二個國家上線的跨境的商品搜尋引擎,是目前台灣, 南美跟東南亞最大的比價服務網站。\n我們正在為全球市場招募[[[Python]]] 資料科學家\n\n我們擁有人類史上數量級最龐大的全球商品資料庫",
"image": "https:\/\/static.104.com.tw\/b_profile\/cust_picture\/3773\/130000000113773\/custintroduce\/image1.jpg?v=20201003032418",
"url": "https:\/\/www.104.com.tw\/job\/7op6q?jobsource=google_event",
"offers": {
"@type": "Offer",
"url": "https:\/\/www.104.com.tw\/company\/1a2x6bkf3h",
"priceCurrency": "TWD",
"description": "免費",
"price": "0",
"availability": "http:\/\/schema.org\/InStock",
"validFrom": "2024\/06\/23"
}
},
{
"@context": "https:\/\/schema.org",
"@type": "Event",
"startDate": "2024\/06\/23",
"eventAttendanceMode": "https:\/\/schema.org\/MixedEventAttendanceMode",
"eventStatus": "EventScheduled",
"name": "Python工程師",
"organizer": {
"@type": "Organization",
"name": "云智資訊股份有限公司",
"url": "https:\/\/www.104.com.tw\/company\/1a2x6bk61j"
},
"location": {
"@type": "Place",
"name": "台北市松山區",
"address": {
"@type": "PostalAddress",
"postalCode": 105
}
},
"performer": {
"@type": "Organization",
"url": "https:\/\/www.104.com.tw\/company\/1a2x6bk61j",
"name": "云智資訊股份有限公司"
},
"description": " Vmware 有使用經驗。\n\n加分條件:\n1. 具備撰寫自動化腳本的經驗,例如:PowerShell、[[[Python]]] 或 Ansible。 \n2. 具備監控服務工具的使用經驗,例如:ELK、Grafana 或 Zabbix , Prometheus",
"image": "https:\/\/static.104.com.tw\/b_profile\/cust_picture\/2039\/130000000102039\/logo.gif?v=20240413194234",
"url": "https:\/\/www.104.com.tw\/job\/80bs8?jobsource=google_event",
"offers": {
"@type": "Offer",
"url": "https:\/\/www.104.com.tw\/company\/1a2x6bk61j",
"priceCurrency": "TWD",
"description": "免費",
"price": "0",
"availability": "http:\/\/schema.org\/InStock",
"validFrom": "2024\/06\/23"
}
},
{
"@context": "https:\/\/schema.org",
"@type": "Event",
"startDate": "2024\/06\/23",
"eventAttendanceMode": "https:\/\/schema.org\/MixedEventAttendanceMode",
"eventStatus": "EventScheduled",
"name": "Python軟體開發工程師",
"organizer": {
"@type": "Organization",
"name": "先傑電腦股份有限公司",
"url": "https:\/\/www.104.com.tw\/company\/ujsf2ow"
},
"location": {
"@type": "Place",
"name": "嘉義縣中埔鄉",
"address": {
"@type": "PostalAddress",
"postalCode": 606
}
},
"performer": {
"@type": "Organization",
"url": "https:\/\/www.104.com.tw\/company\/ujsf2ow",
"name": "先傑電腦股份有限公司"
},
"description": "薪水:大學剛畢業3萬元起+個人資歷+個人化多項津貼\n其他專業及研發人員面議\n熟悉 [[[Python]]]、PostgreSQL 和 ERP作業流程 \n1.使用 [[[Python]]] 語言開發系統經驗(基於ODOO平台開發),有一年以上的經驗更佳\n2.熟悉",
"image": "https:\/\/static.104.com.tw\/b_profile\/cust_picture\/0000\/66500060000\/logo.gif?v=20210709151827",
"url": "https:\/\/www.104.com.tw\/job\/5duf4?jobsource=google_event",
"offers": {
"@type": "Offer",
"url": "https:\/\/www.104.com.tw\/company\/ujsf2ow",
"priceCurrency": "TWD",
"description": "免費",
"price": "0",
"availability": "http:\/\/schema.org\/InStock",
"validFrom": "2024\/06\/23"
}
}
]
</script>
但是其顯示長度是透過偵測滑鼠捲動而動態分頁載入, 因此如果要擷取這些資料必須使用 Selenium 來控制瀏覽器, 模擬滑鼠捲動行為來連續載入不同分頁, 這需要用到 WebDriver 物件的 execute_script() 方法來執行 Javascript 的 window 物件之 scrollTo() 方法.
接下來使用開發者工具觀察執行 Javascript 後所渲染出來的網頁碼來找出網頁元素 (例如表單控制項). 在 Chrome 按 F12 開啟開發者工具, 然後重新整理 104 官網, 切到 Element 頁嵌搜尋輸入框的預設文字開頭 "關鍵字" 可找到上方的搜尋功能是放在一個 id=js-search-form 的表單內 :
<form class="l-container main-search__form"
id="js-search-form"
autocomplete="off">
<div class="b-fake-input">
<div class="b-search-block--l">
<input
type="text"
placeholder="關鍵字(例:職稱、公司名、技能專長...)"
id="keyword"
data-temp=""
maxlength="50">
<svg class="icon-clear b-icon--weak-gray b-icon--w16"
xmlns="http://www.w3.org/2000/svg">
<use xlink:href="//www.104.com.tw/jobs/search/static/img/sprite.svg#icon-clear">
</use>
</svg>
</div>
<span class="b-divide"></span>
<div class="b-search-block--m">
<input type="text" readonly placeholder="地區" id="area-cat"
data-temp="">
<svg class="b-icon--weak-gray b-icon--w16">
<use xlink:href="//www.104.com.tw/jobs/search/static/img/sprite.svg#icon-arrow-down">
</use>
</svg>
</div>
<span class="b-divide"></span>
<div class="b-search-block--m">
<input type="text" readonly placeholder="職務類別" id="job-cat"
data-temp="">
<svg class="b-icon--weak-gray b-icon--w16">
<use xlink:href="//www.104.com.tw/jobs/search/static/img/sprite.svg#icon-arrow-down">
</use>
</svg>
</div>
</div>
<button class="b-btn b-btn--primary is-large gtm-main-search" type="submit">
搜尋
</button>
<div id="search-relative" class="main-search__auxiliary">
<label>
<input type="checkbox" id="only-title" class="b-checkbox">
只搜尋職務名稱
</label>
<p class="js-related-keyword js-related-keyword--related related-keyword">
相關搜尋:
<span>
<a href="//www.104.com.tw/jobs/search/?ro=0&kwop=7&expansionType=area%2Cspec%2Ccom%2Cjob%2Cwf%2Cwktm&order=15&asc=0&page=3&mode=s&langFlag=0&langStatus=0&recommendJob=1&hotJob=1&remoteWork=&irsTag=&label=&keyword=%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB&jobsource=keyword2Keyword"
class="b-link--gray">
軟體工程師
</a>
</span>
<span>
<a href="//www.104.com.tw/jobs/search/?ro=0&kwop=7&expansionType=area%2Cspec%2Ccom%2Cjob%2Cwf%2Cwktm&order=15&asc=0&page=3&mode=s&langFlag=0&langStatus=0&recommendJob=1&hotJob=1&remoteWork=&irsTag=&label=&keyword=C%2B%2B&jobsource=keyword2Keyword"
class="b-link--gray">
C++
</a>
</span>
<span>
<a href="//www.104.com.tw/jobs/search/?ro=0&kwop=7&expansionType=area%2Cspec%2Ccom%2Cjob%2Cwf%2Cwktm&order=15&asc=0&page=3&mode=s&langFlag=0&langStatus=0&recommendJob=1&hotJob=1&remoteWork=&irsTag=&label=&keyword=Linux&jobsource=keyword2Keyword"
class="b-link--gray">
Linux
</a>
</span>
<span>
<a href="//www.104.com.tw/jobs/search/?ro=0&kwop=7&expansionType=area%2Cspec%2Ccom%2Cjob%2Cwf%2Cwktm&order=15&asc=0&page=3&mode=s&langFlag=0&langStatus=0&recommendJob=1&hotJob=1&remoteWork=&irsTag=&label=&keyword=%E8%B3%87%E8%A8%8A%E5%B7%A5%E7%A8%8B&jobsource=keyword2Keyword"
class="b-link--gray">
資訊工程
</a>
</span>
<span>
<a href="//www.104.com.tw/jobs/search/?ro=0&kwop=7&expansionType=area%2Cspec%2Ccom%2Cjob%2Cwf%2Cwktm&order=15&asc=0&page=3&mode=s&langFlag=0&langStatus=0&recommendJob=1&hotJob=1&remoteWork=&irsTag=&label=&keyword=Java&jobsource=keyword2Keyword"
class="b-link--gray">
Java
</a>
</span>
</p>
<label>
<input id="job-type"
type="checkbox"
class="b-checkbox gtm-highend-switch">切換高階職類
</label>
</div>
</form>
輸入框的 id 為 keyword, 但搜尋按鈕卻沒有足以定位它的 id, name 或單一的 class, 因此只好去 Element 頁籤搜尋 "搜尋" 找到此按鈕, 然後按滑鼠右鍵選取 "Copy/Copy XPATH" 取得其 XPATH 為 '//*[@id="js-search-form"]/button' :
照之前的爬蟲經驗, 有了這些資訊應該就能用 Selenium 來操控瀏覽器了. 但其實不然, 因為上面透過開發者工具找出的表單控制項在 Selenium 開啟的瀏覽器中用 find_element() 或 find_elements() 尋找根本就不存在, 這很奇怪, 我也還不清楚原因是甚麼, 因為我對 Vue.js 生成網頁碼的方式並不了解, 我最後是利用 WebDriver 物件的 page_source 屬性內容才找到 Vue.js 實際渲染出來的網頁元素 (如下所示).
二. 用 Selenium 擷取目標網頁 :
首先載入 Selenium :
>>> from selenium import webdriver
>>> driver=webdriver.Firefox()
>>> driver.implicitly_wait(20)
>>> url='https://www.104.com.tw/'
>>> driver.get(url)
然後在 WebDriver 物件中尋找上面利用開發者工具的 Element 頁籤找到的網頁表單 id=js-search-form 以及其內 id=keyword 的輸入框 :
>>> search_form=driver.find_element('id', 'js-search-form')
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="js-search-form"];
>>> keyword=driver.find_element('id', 'keyword')
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="keyword"];
以前用 Selenium 爬過的網站沒遇到過這種情形, 用開發者工具搜尋 Element 找到的渲染後元素基本上都可以用 find_element() 方法找到才對, 但這個用 Vue.js 生成的網頁卻找不到. 於是我將 WebDriver 物件的 page_source 屬性值寫到 htm 檔來觀察 :
>>> with open('104.htm', 'w', encoding='utf-8') as f:
f.write(driver.page_source)
130693
用記事本開啟 104.htm 搜尋 "關鍵字" 找到如下完全不同於上面在 Element 頁籤中所看到的網頁碼, 其中所有元素的屬性名稱都是自訂的 (應該是 Vue.js 產生的), 不僅 form 元素沒有 id=js-search-form 屬性值, 關鍵字輸入框的 input 元素也沒有 id=keyword 屬性值, 難怪用 find_element() 會找不到 :
將這個表單用 "HTML Formatter" 整理後得到下面可讀性較高的格式化後之 HTML 碼 :
<form data-v-34b0b8e5="" class="">
<div data-v-34b0b8e5="" class="">
<div data-v-34b0b8e5="" class="row">
<div data-v-34b0b8e5="" class="col col-10">
<div data-v-34b0b8e5="" class="input-group input-group--search">
<i data-v-34b0b8e5="" class="jb_icon_delete input-group__input-clear d-none"></i><input data-v-34b0b8e5="" data-gtm-index="搜尋欄位-搜尋點擊" type="text" class="form-control" placeholder="關鍵字(例:工作職稱、公司名、技能專長...)" maxlength="50">
<div data-v-34b0b8e5="" class="input-group-append"><button data-v-34b0b8e5="" data-gtm-index="搜尋欄位-搜尋點擊" class="btn btn-sm btn-block d-flex justify-content-between align-items-center" type="button"><span data-v-34b0b8e5="" class="h3">地區</span><i data-v-34b0b8e5="" class="jb_icon_down"></i></button></div>
<div data-v-34b0b8e5="" class="input-group-append"><button data-v-34b0b8e5="" data-gtm-index="搜尋欄位-搜尋點擊" class="btn btn-sm btn-block d-flex justify-content-between align-items-center" type="button"><span data-v-34b0b8e5="" class="h3">職務類別</span><i data-v-34b0b8e5="" class="jb_icon_down"></i></button></div>
<div data-v-34b0b8e5="" class="nav__search nav__search--recent overflow-hidden" style="display: none;">
<ul data-v-34b0b8e5="" class="list-group">
<li data-v-34b0b8e5="" class="list-group-item">
<adsmart-ui-switch data-v-34b0b8e5="" ads-shape="pc" ads-board-identify="pc_c_index_sponsor_company" class="d-flex align-items-center adsmart-item pl-3 adsmart-ui--mounted"></adsmart-ui-switch>
</li>
</ul>
</div>
<div data-v-34b0b8e5="" class="nav__search nav__search--auto-complete overflow-hidden" style="display: none;">
<ul data-v-34b0b8e5="" class="list-group" style="display: none;"></ul>
<ul data-v-34b0b8e5="" class="list-group" style="display: none;">
<li data-v-34b0b8e5="" class="list-group-item list-group-item--addon t4"> 公司 </li>
</ul>
<ul data-v-34b0b8e5="" class="list-group d-none mt-2">
<li data-v-34b0b8e5="" class="list-group-item row no-gutters">
<adsmart-ui-switch data-v-34b0b8e5="" ads-shape="pc" ads-board-identify="pc_c_index_keywords_job" ads-keyword-input="" class="d-flex align-items-center adsmart-item pl-3"></adsmart-ui-switch>
</li>
</ul>
<ul data-v-34b0b8e5="" class="list-group">
<li data-v-34b0b8e5="" class="list-group-item list-group-item--addon t4"> 公司 </li>
<li data-v-34b0b8e5="" class="list-group-item row no-gutters"><a data-v-34b0b8e5="" class="col jb-link jb-link-blue t4" href="//www.104.com.tw/company/search/?jobsource=index_s_ac"> 更多 <b data-v-34b0b8e5=""></b> 相關公司 </a></li>
</ul>
</div>
</div>
</div>
<div data-v-34b0b8e5="" class="col col-2"><button data-v-34b0b8e5="" data-gtm-index="搜尋欄位-搜尋點擊" class="btn btn-secondary btn-block btn-lg" type="submit"> 搜尋 </button></div>
</div>
</div>
</form>
觀察此 HTML 碼可知 "關鍵字" 輸入框有一個樣式類別名稱 "form-control", 且搜尋整個渲染後的網頁只有這個元素使用此樣式類別, 因此可以直接用 find)element() 找到此輸入框物件, 然後呼叫其 send_keys() 方法傳入 "Python" :
>>> keyword=driver.find_element('class name', 'form-control')
>>> keyword.send_keys('Python')
接下來只要模擬按下搜尋鈕的動作即可, 但此 submit 按鈕卻沒有 id, name 或足以單獨識別它的 class 屬性, 因此嘗試用搜尋所有標籤名為 button 的元素, 然後逐一利用 text 屬性與 get_attribute() 方法找出其索引 :
>>> buttons=driver.find_elements('tag name', 'button') # 搜尋全部按鈕元素物件
檢查索引 0 與 1 都不是 "搜尋" 鈕 :
>>> buttons[0].text
'地區'
>>> buttons[1].text
'職務類別'
>>> buttons[2].text # 是 "搜尋" 鈕
'搜尋'
檢查 type 與 data-gtm-index 屬性值 :
>>> buttons[2].get_attribute('type')
'submit'
>>> buttons[2].get_attribute('data-gtm-index')
'搜尋欄位-搜尋點擊'
這樣即確認 "搜尋" 鈕索引為 2 無誤, 呼叫其 click() 方法進行搜尋 :
>>> buttons[2].click()
這時 Selenium 開啟的 Firefox 瀏覽器就會列出搜尋結果, 從上方的下拉式選單可知總共有 150 頁, 現在顯示的是第一頁 :
檢視開發人員工具的 Element 頁籤可知這些工作機會項目是放在一個 id="js-job-content" 的 div 元素內, 每個項目就是一個 article 元素 :
本爬蟲的目標是抓出上圖紅框中的資訊, 亦即 article 的 data-job-name 與 data-cust-name 的屬性值, 還有裡面指向此項目詳細說明頁面的超連結.
上圖中我們在 Element 頁籤內搜尋 "<article" 發現共有 24 個 article 元素, 用 find_elements() 方法去找也是 24 個, 這表示每頁會顯示 24 個項目 :
>>> articles=driver.find_elements('tag name', 'article')
>>> len(articles)
24
這樣就可以取得各個項目的目標資料了, 第一個項目如下 :
>>> articles[0].get_attribute('data-job-name')
'[暑期實習]量化交易策略研發實習生(※請至官網下載申請表並投遞至專屬信箱※)'
>>> articles[0].get_attribute('data-cust-name')
'統一綜合證券股份有限公司'
>>> link=articles[0].find_element('tag name', 'a')
>>> link.get_attribute('href')
'https://www.104.com.tw/job/8daij?jobsource=hotjob_chr'
下面是第二個項目 :
>>> articles[1].get_attribute('data-job-name')
'【招聘赴日東京】軟件開發工程師(待遇優厚,辦理簽證)'
>>> articles[1].get_attribute('data-cust-name')
'株式会社ブライトスター'
>>> link=articles[1].find_element('tag name', 'a')
>>> link.get_attribute('href')
'https://www.104.com.tw/job/8d8yl?jobsource=hotjob_chr'
此頁最後一個項目 :
>>> articles[23].get_attribute('data-cust-name')
'磐弈有限公司'
>>> link=articles[23].find_element('tag name', 'a')
>>> link.get_attribute('href')
'https://www.104.com.tw/job/8bek0?jobsource=hotjob_chr'
當滑鼠滾輪往下移動時會不斷載入後續頁的項目 (往下疊, 前面的不會消失), 這相當於是執行 Javascript 的 window.scrollTo() 方法將滑鼠滾輪移動整個視窗高度的效果, 可以用 WebDriver 的 execute_script() 方法來執行 Javascript 移動滑鼠的程式碼 :
>>> js='window.scrollTo(0, document.body.scrollHeight)' # 移動滑鼠滾輪視窗高度
>>> driver.execute_script(js)
這時再去搜尋 article 元素會發現它大約增加一倍 :
>>> articles=driver.find_elements('tag name', 'article')
>>> len(articles)
46
預期是 48 個, 但只有 46 個, 且其中有兩個 article 內容是空的, 檢查前面第一頁的資料還是一樣 :
>>> articles[0].get_attribute('data-job-name')
'[暑期實習]量化交易策略研發實習生(※請至官網下載申請表並投遞至專屬信箱※)'
>>> articles[1].get_attribute('data-job-name')
'【招聘赴日東京】軟件開發工程師(待遇優厚,辦理簽證)'
>>> articles[23].get_attribute('data-job-name')
'助理工程師'
下面是呼叫 execute_script() 執行模擬移動滑鼠滾輪後載入的第二頁內容, 但最後兩個 article (索引 44, 45) 內容卻是空的 :
>>> articles[24].get_attribute('data-job-name')
'(Sr.) Automation Test Engineer(Python) - Tainan/Hybrid'
>>> articles[25].get_attribute('data-job-name')
'Python 軟體工程師 (台北市 內湖)'
... (略) ...
>>> articles[42].get_attribute('data-job-name')
'Python 軟體工程師'
>>> articles[43].get_attribute('data-job-name')
'Python 軟體工程師'
>>> articles[44].get_attribute('data-job-name') # 傳回 None
>>> articles[45].get_attribute('data-job-name') # 傳回 None
現在已載入兩頁資料, 所以我們只要再呼叫三次 execute_script() 就可以載入 5 頁的資料了 :
>>> for i in range(3):
js='window.scrollTo(0, document.body.scrollHeight)'
driver.execute_script(js)
重新搜尋 article 元素物件 :
>>> articles=driver.find_elements('tag name', 'article')
>>> len(articles)
68
預期 5 頁應該有 100 多筆, 卻只有 68 筆. 這可能是迴圈跑太快, 執行 Javascript 滑動滑鼠滾輪需要亦點時間之故, 因此可能需要在迴圈底用 time.sleep(0.5) 休息個半秒鐘. 檢視前面幾頁資料都沒變 , 可見滑動滑鼠滾輪是載入更多分頁往下疊, 不是覆蓋 :
>>> articles[0].get_attribute('data-job-name')
'[暑期實習]量化交易策略研發實習生(※請至官網下載申請表並投遞至專屬信箱※)'
>>> articles[1].get_attribute('data-job-name')
'【招聘赴日東京】軟件開發工程師(待遇優厚,辦理簽證)'
>>> articles[23].get_attribute('data-job-name')
'助理工程師'
>>> articles[25].get_attribute('data-job-name')
'Python 軟體工程師 (台北市 內湖)'
>>> articles[43].get_attribute('data-job-name')
'Python 軟體工程師'
>>> articles[65].get_attribute('data-job-name')
'AJ6-後端/全端軟體開發工程師(Python)'
最後兩筆是空的 :
>>> articles[66].get_attribute('data-job-name') # 傳回 None
>>> articles[67].get_attribute('data-job-name') # 傳回 None
如果要抓出搜尋結果的總頁數, 可以從網頁上方 class 屬性值為 "gtm-paging-top" 的下拉式選單的取得 (具有此屬性值者只有一個) :
先用 class='gtm-paging-top' 搜尋 select 元素之物件, 然後再搜尋其下的第一個 option 物件 :
>>> page_select=driver.find_element('class name', 'gtm-paging-top')
>>> page_option=page_select.find_element('tag name', 'option')
>>> page_option.text
'第 1 / 150 頁'
這樣便取得包含總頁數之字串了, 接著將 text 屬性值以 '/' 為界拆分取第二個索引, 然後用正規式將其中的數字 150 抓出來 :
>>> pages_str=page_option.text.split('/')[1]
>>> pages_str
' 150 頁'
>>> pages=re.findall(r'\d+', pages_str)[0]
>>> pages
'150'
如果要載入全部分頁的資料, 可以用 pages 當迴圈終點 :
>>> import time
>>> for i in range((int(pages)):
js='window.scrollTo(0, document.body.scrollHeight)'
driver.execute_script(js)
time.sleep(0.5)
以上測試之完整程式碼如下 :
from selenium import webdriver
import re
import time
driver=webdriver.Firefox()
driver.implicitly_wait(20)
url='https://www.104.com.tw/'
driver.get(url)
keyword=driver.find_element('class name', 'form-control')
keyword.send_keys('Python')
buttons=driver.find_elements('tag name', 'button')
buttons[2].click()
for i in range(5):
js='window.scrollTo(0, document.body.scrollHeight)'
driver.execute_script(js)
time.sleep(0.5)
articles=driver.find_elements('tag name', 'article')
print(len(articles))
for i in range(len(articles)):
job_name=articles[i].get_attribute('data-job-name')
cust_name=articles[i].get_attribute('data-cust-name')
if job_name:
print(f'工作名稱: {job_name}')
print(f'徵求公司: {cust_name}')
link=articles[i].find_element('tag name', 'a')
url=link.get_attribute('href')
print(f'詳細資訊: {url}\n')
else:
print(f'索引 {i} 無資料\n')
driver.close()
執行結果 :
>>> %Run 104_job_search_1.py
132
工作名稱: [暑期實習]量化交易策略研發實習生(※請至官網下載申請表並投遞至專屬信箱※)
徵求公司: 統一綜合證券股份有限公司
詳細資訊: https://www.104.com.tw/job/8daij?jobsource=hotjob_chr
工作名稱: 廣告行銷數據分析師 Marketing Data Analyst
徵求公司: 雅德思行銷顧問有限公司
詳細資訊: https://www.104.com.tw/job/8d6bk?jobsource=hotjob_chr
工作名稱: Python Data Scientist
徵求公司: BigGo_樂方股份有限公司
詳細資訊: https://www.104.com.tw/job/7op6q?jobsource=index_s
工作名稱: Python工程師
徵求公司: 創昇資訊有限公司
詳細資訊: https://www.104.com.tw/job/82dtp?jobsource=index_s
工作名稱: Python軟體工程師
徵求公司: 奇力速工業股份有限公司
詳細資訊: https://www.104.com.tw/job/6vayl?jobsource=index_s
工作名稱: Python工程師
徵求公司: 云智資訊股份有限公司
詳細資訊: https://www.104.com.tw/job/80bs8?jobsource=index_s
工作名稱: Python軟體開發工程師
徵求公司: 先傑電腦股份有限公司
詳細資訊: https://www.104.com.tw/job/5duf4?jobsource=index_s
... (略) ...
工作名稱: 機器學習工程師 AI / ML Engineer (高雄)
徵求公司: 資旅軟體開發有限公司
詳細資訊: https://www.104.com.tw/job/7jaus?jobsource=index_s
工作名稱: 【雲端系統部】Python工程師 (中和)
徵求公司: 昇銳電子股份有限公司
詳細資訊: https://www.104.com.tw/job/6zloe?jobsource=index_s
工作名稱: T-【2024軟體技術人才招募】Python 工程師_人才招募
徵求公司: 緯創軟體股份有限公司
詳細資訊: https://www.104.com.tw/job/69za2?jobsource=index_s
工作名稱: Python 程式設計語言講師 - 基隆、大台北地區
徵求公司: 聯成電腦有限公司(聯成電腦/聯成外語)
詳細資訊: https://www.104.com.tw/job/5pxb7?jobsource=index_s
索引 130 無資料
索引 131 無資料
如果要取得全部搜尋到的資料, 就把迴圈的終點設為從下拉式選單中取得的總頁數 :
from selenium import webdriver
import re
import time
driver=webdriver.Firefox()
driver.implicitly_wait(20)
url='https://www.104.com.tw/'
driver.get(url)
keyword=driver.find_element('class name', 'form-control')
keyword.send_keys('Python')
buttons=driver.find_elements('tag name', 'button')
buttons[2].click()
page_select=driver.find_element('class name', 'gtm-paging-top')
page_option=page_select.find_element('tag name', 'option')
pages_str=page_option.text.split('/')[1]
pages=int(re.findall(r'\d+', pages_str)[0])
print(f'總頁數: {pages}')
for i in range(pages):
js='window.scrollTo(0, document.body.scrollHeight)'
driver.execute_script(js)
time.sleep(0.5)
articles=driver.find_elements('tag name', 'article')
print(f'總筆數: {len(articles)}')
for i in range(len(articles)):
job_name=articles[i].get_attribute('data-job-name')
cust_name=articles[i].get_attribute('data-cust-name')
if job_name:
print(f'{i}. {job_name}')
print(f'❖ {cust_name}')
link=articles[i].find_element('tag name', 'a')
url=link.get_attribute('href')
print(f'▶ {url}\n')
else:
print(f'索引 {i} 無資料\n')
driver.close()
但是不知何因 (我猜是瀏覽器記憶體限制), 模擬滑動滑鼠滾輪的動作到第 15 頁時都會停住, 所以只載入 311 筆資料 :
>>> %Run 104_job_search_2.py
總頁數: 150
總筆數: 311
... (略) ...
306. PHP、Perl 程式設計工程師(新竹)
❖ 桓基科技股份有限公司
▶ https://www.104.com.tw/job/4o46l?jobsource=index_s
307. Business Insight Analyst - 數據分析專員
❖ 美商泰優股份有限公司台灣分公司
▶ https://www.104.com.tw/job/6a28o?jobsource=index_s
308. 軟體工程師/系統維護工程師
❖ 偉福科技有限公司
▶ https://www.104.com.tw/job/8acdh?jobsource=index_s
309. 後端工程師
❖ 神瑞人工智慧股份有限公司
▶ https://www.104.com.tw/job/827of?jobsource=index_s
索引 310 無資料
索引 311 無資料
參考 :
沒有留言 :
張貼留言