前陣子被公司抓去當了QA,不得不說QA真的很無聊,就是一直在按一些button,大概就是一些重複性高,又沒什麼產值的工作,剛好想到之前在前前公司有聽過Selenium這個東西,於是來研究一下。
介紹
可以先看一下底下的gif,就可以更了解selenium的作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import time
from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.google.com/')
search = driver.find_element(By.NAME, 'q')
search.send_keys('武嶺')
search.send_keys(Keys.ENTER)
time.sleep(100)
|
這邊這段程式,就是指
然後Chrome就會自己動起來了。有時候我們可能需要某些網站的資訊,但它本身不是走前後端分離,資訊都是透過session、cookie來傳遞,而不是json,像這種情況,如果對速度沒有特別的要求,就可以使用Selenium來處理。接著我們就以104網頁的爬蟲為範例,示範看看Selenium使用
安裝
老樣子,使用Anaconda
- 創建conda環境
1
| conda create --name selenium python=3.9 -y
|
- 進入環境
1
| conda activate selenium
|
- 安裝所需要的pip
- 查看pip是否安裝成功
開頭
程式碼放在這:https://github.com/Hoxton019030/Selenium
先寫一段Demo用的Code
1
2
3
4
5
6
7
8
| from selenium import webdriver
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.google.com/')
driver.close()
print("成功")
|
效果:
讓我們現在把頁面改成104的頁面
1
2
3
4
5
6
7
8
9
10
| from selenium import webdriver
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.104.com.tw/jobs/search/?keyword=Java&order=1&jobsource=2018indexpoc&ro=0')
driver.close()
print("成功")
|
接下來這樣就可以開啟104的頁面了,那接下來我們就是要透過driver這個物件,來找到我們目前頁面的一些資訊,我們可以使用
來查看頁面的一些資訊
1
2
3
4
5
6
7
8
9
10
11
| from selenium import webdriver
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.104.com.tw/jobs/search/?keyword=Java&order=1&jobsource=2018indexpoc&ro=0')
print(driver.page_source)
driver.close()
print("成功")
|
這個driver.page_source所包含的其實就是整個網頁的DOM,既然有html的東西,那接下來就要介紹各種不同的選擇器,來取得想要的資訊
選擇器
Selenium提供了許多不同的選擇器,讓我們來定位我們的html內容,
接下來會講幾個比較常用的,其實也就是css_selector跟class name,
Class選擇器
我們現在想要找到每份職缺的title
查看DOM之後我們發現,它的html class 為 js-job-link
那我們就可以這樣寫
1
2
3
4
5
6
7
8
9
10
11
12
13
| from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.104.com.tw/jobs/search/?keyword=Java&order=1&jobsource=2018indexpoc&ro=0')
titles = driver.find_elements(By.CLASS_NAME, "js-job-link")
for title in titles:
print(title.text)
driver.close()
|
效果:
CSS 選擇器
這是Selenium官方最為推薦的選擇方式,最大的優勢就在於它可以使用css選擇器來定位元素,比如說nth-child、first-child之類的,所以它可以找到幾乎所有的元素,比如說我想要找到工作的描述
它的css選擇器就是這樣讀
「想要找到tag為p,並且有job-list-item__info, b-clearfix, b-content 這些class名稱的地方」
那python就是這樣寫
1
2
3
4
5
6
7
8
9
10
11
12
13
| from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.104.com.tw/jobs/search/?keyword=Java&order=1&jobsource=2018indexpoc&ro=0')
titles = driver.find_elements(By.CLASS_NAME, "js-job-link")
jobInfos = driver.find_elements(By.CSS_SELECTOR, "p.job-list-item__info.b-clearfix.b-content")
for element in jobInfos:
print(element.text)
driver.close()
|
其實觀察一下可以發現,Google的開發人員工具已經幫我們把這個地方的css selector標出來了
我們可以直接複製貼上就好
剛剛有提到說可以使用css selector來做更仔細的定位,那麼該怎麼用呢?以104中的工作要求為例
我們注意到,它其實是一個ul的結構,想要挑出「工作經驗」這一個區塊的東西,想要用class選擇器來定位基本上不可能,這時候就要使用css selector來達到我們想要的效果,而這邊的寫法就是這樣
1
| experiences = driver.find_elements(By.CSS_SELECTOR,"ul.b-list-inline.b-clearfix.job-list-intro.b-content li:nth-child(3)")
|
來通過選擇器找到li的第三個child,也就是工作經歷
ID 選擇器
id選擇器就是id選擇,使用方式就如同大家想的那樣,通常是用來定位一些unique的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.104.com.tw/jobs/search/?keyword=Java&order=1&jobsource=2018indexpoc&ro=0')
titles = driver.find_elements(By.CLASS_NAME, "js-job-link")
jobInfos = driver.find_elements(By.CSS_SELECTOR, "p.job-list-item__info.b-clearfix.b-content")
experiences = driver.find_elements(By.CSS_SELECTOR,
"ul.b-list-inline.b-clearfix.job-list-intro.b-content li:nth-child(3)")
mainContent = driver.find_element(By.ID, "main-content")
print(mainContent.text)
driver.close()
|
透過element來找到裡面的attribute
現在想要把每個工作的超連結取出來,要怎麼做呢?
可以先透過選擇器選出想要的tag,變成一個webElement物件(相當於底下的hrefs),然後再透過get_attribute來取得標籤內的值,我們就可以透過這樣的方式來取出想要的資料了
1
2
3
4
5
| hrefs = driver.find_elements(By.CLASS_NAME, "js-job-link")
for href in hrefs:
link = href.get_attribute('href')
print(link)
|
點擊的觸發
當我們取得這個頁面的資料完後,想要接著點擊下一頁,那我們該怎麼做呢?
首先先透過選擇器,來選到下一頁的button
1
| link = driver.find_element(By.CLASS_NAME, "js-next-page")
|
接著可以透過link.click()來觸發點擊事件,切換到下一頁
疑!!幹幹幹幹幹,怎麼出錯了
看了一下console區,它的回應是這樣
「網頁中沒有可以互動的元素」,像這種情況多半是因為DOM元件還沒load進來,我們可以透過一些方式來延緩它加載的時間,最爛的做法其實就是用time.sleep()的方式來,但因為我現在只會用這個方式(✿◡‿◡)
1
2
3
4
5
6
7
8
9
10
11
12
| from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get('https://www.104.com.tw/jobs/search/?keyword=Java&order=1&jobsource=2018indexpoc&ro=0')
for i in range(10):
time.sleep(2)
link = driver.find_element(By.CLASS_NAME, "js-next-page")
link.click()
|
實際的程式碼
最後我們把上面的東西整合一下,並且把資料存到excel裡面,就可以達到我們想要的效果嚕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| import time
import openpyxl
from selenium import webdriver
from selenium.webdriver.common.by import By
from JobDetail import JobDetail
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
options.add_argument("--test-type")
options.binary_location = "/usr/bin/chromium"
driver = webdriver.Chrome()
url = "https://www.104.com.tw/jobs/search/?https://www.104.com.tw/jobs/search/?ro=0&kwop=7&keyword=Java&expansionType=area%2Cspec%2Ccom%2Cjob%2Cwf%2Cwktm&area=6001001001%2C6001001007%2C6001002000%2C6001001003%2C6001001006%2C6001001002%2C6001001012%2C6001001004%2C6001001005&order=15&asc=0&page=1&mode=s&jobsource=2018indexpoc&langFlag=0&langStatus=0&recommendJob=1&hotJob=1"
driver.get(url)
driver.implicitly_wait(10)
workbook = openpyxl.Workbook()
sheet = workbook.active
headers = ["職務名稱", "公司名稱", "相關描述", "工作簡述", "公司地址", "工作經驗要求", "網址連結"]
sheet.append(headers)
for i in range(113): # 頁面的interator
try:
titles = driver.find_elements(By.CLASS_NAME, "js-job-link")
companyNames = driver.find_elements(By.CSS_SELECTOR, "ul.b-list-inline.b-clearfix a")
hrefs = driver.find_elements(By.CLASS_NAME, "js-job-link")
# 這邊先註解掉,不知道怎麼把薪資抓出來
# tags = driver.find_elements(By.CLASS_NAME, "b-tag--default")
# stringTag = ""
# for tag in tags:
# stringTag += tag.text
# jobInfos = driver.find_elements(By.CLASS_NAME, "job-list-item__info")
jobInfos = driver.find_elements(By.CSS_SELECTOR, "p.job-list-item__info.b-clearfix.b-content")
# for jobInfo in jobInfos:
# print(jobInfo.text)
addresses = driver.find_elements(By.CSS_SELECTOR,
"ul.b-list-inline.b-clearfix.job-list-intro.b-content li:first-child")
experiences = driver.find_elements(By.CSS_SELECTOR,
"ul.b-list-inline.b-clearfix.job-list-intro.b-content li:nth-child(3)")
job_details_list = []
for j in range(len(titles)):
name = ""
title = titles[j].text
if "/" in title:
break
companyName = companyNames[j].text
href = hrefs[j].get_attribute('href')
jobInfo = jobInfos[j].text
address = addresses[j].text
experience = experiences[j].text
job_detail = JobDetail(title, companyName, href, name, jobInfo, address, experience)
job_details_list.append(job_detail)
for job in job_details_list:
rowData = [job.title, job.companyName, job.tags, job.jobInfo, job.address, job.experience, job.href]
sheet.append(rowData)
link = driver.find_element(By.CLASS_NAME, "js-next-page")
link.click()
time.sleep(2)
print("目前處理到第", i, "頁")
workbook.save("test.xlsx")
print("儲存")
except Exception as e:
print(e)
time.sleep(1)
continue
print("儲存")
workbook.close()
# for some in hrefs:
# print(some.get_dom_attribute('href'))
# print(titles)
# time.sleep(10)
# a = JobDetail()
# for title in titles:
# print(title.text)
# for companyName in companyNameList:
# print(companyName.text)
# for tag in tags:
# print(tag.text)
# print(driver.page_source)
# 取得上一頁的文章標題
# time.sleep(10)
# driver.close()
|
這樣就可以囉!