selenium 是什么?#
Selenium 是支持 web 浏览器自动化的一系列工具和库的综合项目。
它提供了扩展来模拟用户与浏览器的交互,用于扩展浏览器分配的分发服务器, 以及用于实现 W3C WebDriver 规范 的基础结构, 该 规范 允许您为所有主要 Web 浏览器编写可互换的代码。
python 中的 selenium 库是 selenium 的接口,它可以模拟浏览器像人一样操作页面,获取网页信息.
基于这个特点,selenium 在爬取某些网站信息时代码逻辑更简单,且不用逆向 js 加密代码.
然而,因为是模拟操作,所以爬取效率比不上其他爬虫.
为了展现 selenium 的强大,我们举个例子:
爬取 bilibili 个人页面的 粉丝名字 及 粉丝的粉丝数
注意: 爬取数据时要注意网站的 robots.txt 内的规定,同时不要有太高的爬取频率,以免对网站产生负担。本文爬取的 粉丝名字 与 粉丝的粉丝数 属于公开内容.
安装#
$ pip install selenium
分析网站#
在个人空间的粉丝页面下,粉丝信息位于<ul>
下的<li>
元素内.
然而在<li>
内没有粉丝的粉丝数这一信息,而是在服务器上,没有储存在本地。它是通过鼠标移动到粉丝头像或者名字后,触发 js 传输到本地的。这个操作是由 AJAX 技术实现的.
AJAX(Asynchronous JavaScript and XML) 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术.
在鼠标移动到头像上时,会在<body>
末端生成一个<div id="id-card">
:
粉丝的粉丝数位于<div id="id-card">
下的<span class="idc-meta-item">
下:
匹配方法#
selenium 内匹配元素有很多方法:
- xpath (最常用)
- by id
- by name/tag name/class name
- by link
- by css selector
xpath 之所以好用是因为 xpath 可以使用相对路径匹配,并且语法简单.
比如匹配粉丝头像可以这样写:
//div[@id="id-card"]
而在 XML 下该元素的位置:
<html>
...
<body>
...
<div id = "id-card">
...
</div>
</body>
</html>
当然 css selector 有时也很好用:
XML:
<html>
<body>
<p class="content">Site content goes here.</p>
</body>
<html>
css selector:
p.content
写爬虫#
初始化:
def initDriver(url):
#设置headless浏览器
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_experimental_option('excludeSwitches', ['enable-logging'])
#初始化
driver = webdriver.Chrome(options=options)
actions = ActionChains(driver)
#打开链接
driver.get(url)
driver.implicitly_wait(10)
return driver, actions
获取页码:
def getPageNum(driver):
#通过xpath匹配底部翻页元素位置, 获取页码数
text = driver.find_element("xpath", '//ul[@class="be-pager"]/span[@class="be-pager-total"]')
.get_attribute("textContent")
.split(' ')
return text[1]
遍历所有页:
def spawnCards(page, driver, actions):
#遍历所有页
for i in range(1,int(page) + 1):
print(f"get data in page {i}\n")
#触发ajax生成card
spawn(driver, actions)
if (i != int(page)):
#翻页
goNextPage(driver, actions)
time.sleep(6)
生成 card:
def spawn(driver, actions):
#得到card list
ulList = driver.find_elements("xpath", '//ul[@class="relation-list"]/li')
#生成 card
for li in ulList:
getCard(li, actions)
time.sleep(2)
def getCard(li, actions):
cover = li.find_element("xpath", './/a[@class="cover"]')
actions.move_to_element(cover)
actions.perform()
actions.reset_actions()
获取并储存数据:
def writeData(driver):
#获取 card list
cardList = driver.find_elements("xpath", '//div[@id="id-card"]')
for card in cardList:
up_name = card.find_element("xpath", './/img[@class="idc-avatar"]').get_attribute("alt")
up_fansNum = card.find_elements('css selector','span.idc-meta-item')[1].get_attribute("textContent")
print(f'name:{up_name}, {up_fansNum}')
#写入csv文件
with open('.\\date.csv', mode='a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([up_name, up_fansNum])
完整代码:
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time
import csv
def initDriver(url):
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_experimental_option('excludeSwitches', ['enable-logging'])
driver = webdriver.Chrome(options=options)
actions = ActionChains(driver)
driver.get(url)
driver.get(url)
driver.implicitly_wait(10)
return driver, actions
def getPageNum(driver):
text = driver.find_element("xpath", '//ul[@class="be-pager"]/span[@class="be-pager-total"]').get_attribute("textContent").split(' ')
return text[1]
def goNextPage(driver, actions):
bottom = driver.find_element("xpath", '//li[@class="be-pager-next"]/a')
actions.click(bottom)
actions.perform()
actions.reset_actions()
def getCard(li, actions):
cover = li.find_element("xpath", './/a[@class="cover"]')
actions.move_to_element(cover)
actions.perform()
actions.reset_actions()
def writeData(driver):
#get card list
cardList = driver.find_elements("xpath", '//div[@id="id-card"]')
for card in cardList:
up_name = card.find_element("xpath", './/img[@class="idc-avatar"]').get_attribute("alt")
up_fansNum = card.find_elements('css selector','span.idc-meta-item')[1].get_attribute("textContent")
print(f'name:{up_name}, {up_fansNum}')
#write info into csv file
with open('.\\date.csv', mode='a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([up_name, up_fansNum])
def spawn(driver, actions):
#get card list
ulList = driver.find_elements("xpath", '//ul[@class="relation-list"]/li')
#spawn card
for li in ulList:
getCard(li, actions)
time.sleep(2)
def spawnCards(page, driver, actions):
for i in range(1,int(page) + 1):
print(f"get data in page {i}\n")
spawn(driver, actions)
if (i != int(page)):
goNextPage(driver, actions)
time.sleep(6)
def main():
#init driver
uid = input("bilibili uid:")
url = "https://space.bilibili.com/" + uid + "/fans/fans"
driver, actions = initDriver(url)
page = getPageNum(driver)
#spawn card info(ajax)
spawnCards(page, driver, actions)
writeData(driver)
driver.quit()
if __name__ == "__main__":
main()
结果#
反思#
可以改进的地方:
- 由于 ajax 异步加载,必须等待页面加载完毕后才能进行元素定位。而使用
time.sleep()
方法不够效率和优雅,WebDriverWait()
方法可以解决。它可以轮询页面状态,当页面加载完毕返回true
. - 使用了多次含重复路径的 xpath 表达式,匹配占用了太多内存.
- 完全可以并发的提取数据,更快的获取结果。但出于对服务器负荷的考虑,只写了单线程版本.
References#
selenium doc
推荐阅读:
Ajax
Xpath