Web Spider based on Selenium

基于Selenium的网络爬虫。

Background

突然需要一个爬虫爬取一个页面上的部分信息,但是我没有很高深的有关爬虫,html,js 方面的技术和知识,只知道获取网页源码使用正则表达式匹配这样的基本原理,遇到使用 js 动态生成元素的网页就没法爬了。

但是使用Selenium即使是对 js 之类的技术一窍不通的小白也可以很迅速的爬取任何在网页上能看到的东西,而且不仅仅能爬取,它还能模拟点击按钮,滑动等操作,以获取更多信息。

需要爬的是慕课网的用户信息,url 长这样

https://www.icourse163.org/home.htm?userId={id}#/home/course

其中括号里的id是每个用户的id,已经有一个包含所有需要爬取用户id的列表,到时候可以遍历。

需要获取的是网页中这些元素:

image-20210204203535899

马赛克模糊的地方是用户名。

目标是遍历id列表,记录每个列表里用户的用户名,职业,主题,获赞,学习时长等信息。

完整代码在最后。

Step1 启动浏览器

首先是基本思路,Selenium是网页自动测试的工具,在本地使用的时候会真的打开一个浏览器在上面操作,如果是在colab或在无图形界面的服务器上操作可以设置为headless模式。

通常的教程会让你去下载Selenium对应的浏览器driver,由于浏览器版本经常升级,每次都去下就很麻烦,因此这里使用了一个名为

webdriver_manager 的模块,可以自动安装匹配的driver, 运行这段代码,就会自动打开一个空白的chrome浏览器,并转到制定的url。

1
2
3
4
5
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(ChromeDriverManager().install()) # 打开一个空白浏览器
driver.get(f"https://www.icourse163.org/home.htm?userId={id}#/home/course")

image-20210204204534699

Step2 定位元素

我们的目标是爬取任何看到的又想要的东西,这里我选择使用xpath进行定位,具体方法是按f12打开开发者工具中的Element面板,按下ctrl + shift + c,进入选择模式,点击想要的那个元素,比如这里我们点击“主题/回复”的数字,于是右侧的Element面板中该数字所在位置就被标识了出来,此时右键它,选择Copy->Copy Xpath 以获取该元素的Xpath进行定位。

image-20210204204909977

image-20210204205118380

得到的Xpath长这样

//*[@id=”j-self-content”]/div/div[5]/div/div/div[7]/span

Step3 获取元素

得到了Xpath,我们就可以获取到该元素中的值,代码长这样

1
2
3
Xpath = '//*[@id="j-self-content"]/div/div[5]/div/div/div[7]/span'  # 这里注意单引号
item = driver.find_elements_by_xpath(Xpath)
text = item[0].text

但是这里有一个问题,由于Selenium是模拟操作浏览器以爬取信息,因此页面没加载好的时候很容易出现爬不到东西的情况,因此需要等它爬到东西后才能心满意足的进行下一步,所以这里写个函数来表达这个逻辑。

1
2
3
4
5
6
7
8
9
10
def get_element(Xpath):
text = ''
count = 0
while text == '':
item = driver.find_elements_by_xpath(Xpath)
text = item[0].text
count += 1
if count >= 10000:
return 'null'
return text

如果一直拿不到东西,那就等着,直到拿到了或者尝试太多次了。

Step4 完整代码

完整代码里包含了两种等待机制, 都是为了防止页面没加载好导致获取不了想要的东西。

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
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import pandas as pd

driver = webdriver.Chrome(ChromeDriverManager().install())

# driver.get("https://www.icourse163.org/home.htm?userId=1034056275#/home/course")

name_list = []
id_list = [10030774, 1003203, 1003438, 10034555, 10037678, 10037679] # id列表


def get_element(Xpath): # 获取元素内容的函数
text = ''
count = 0
while text == '':
item = driver.find_elements_by_xpath(Xpath)
text = item[0].text
count += 1
if count >= 10000:
return 'null'
return text


Xpath_dict = { # 所有要爬的元素和它们的Xpath
'name': '//*[@id="j-self-content"]/div/div[2]/span', # 名字
'job': '//*[@id="j-self-content"]/div/div[3]/span', # 职业
'topic': '//*[@id="j-self-content"]/div/div[5]/div/div/div[7]/span', # 主题/回复
'like': '//*[@id="j-self-content"]/div/div[5]/div/div/div[11]/span', # 获赞数量
'study_time': '//*[@id="j-self-content"]/div/div[5]/div/div/div[3]/span', # 学习时长
}

res_list = []

for id in id_list: # 遍历id列表
driver.get(f"https://www.icourse163.org/home.htm?userId={id}#/home/course")
driver.implicitly_wait(10) # 隐式等待10秒
locator = (By.XPATH, '//*[@id="j-self-content"]/div/div[2]/span')
WebDriverWait(driver, 20, 0.5).until(EC.presence_of_element_located(locator)) # 等到定位好元素后继续下一步

name = get_element(Xpath_dict['name']) # 获取元素内容
job = get_element((Xpath_dict['job']))
topic = get_element((Xpath_dict['topic']))
like = get_element((Xpath_dict['like']))
study_time = get_element((Xpath_dict['study_time']))

res_dict = { # 存入字典
'id': id,
'name': name,
'job': job,
'topic': topic,
'like': like,
'study_time': study_time,
}
print(res_dict)
res_list.append(res_dict) # 将字典存入列表

res_df = pd.DataFrame(res_list) # 将包含字典的列表变为Dataframe,方便操作和存储
print(res_df)