Cross Code

There is only one heroism in the world: to see the world as it is and to love it

0%

项目实战(零二):Selenium实现爬虫与自动化测试

本篇记录的是软件测试方向的项目实战,使用 Selenium 实现爬虫与自动化测试。具体来说则是自动化测试运行了合成大西瓜游戏以及使用 Selenium 爬取京东商城的商品。

一. Selenium 常规操作

(A) 基本请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from selenium import webdriver

# 创建实例
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.xxx()

# 浏览器窗口最大化
browser.maximize_window()

# 请求百度首页
browser.get("https://movie.douban.com/top250")

# 关闭当前页面
browser.close()
# 退出浏览器
browser.quit()

(B) 常用操作

(a) 浏览器方法

浏览器方法 作用
browser.get(url) 请求url
browser.quit() 关闭浏览器
browser.close() 关闭当前页面
browser.page_source 获取源码
browser.page_source.find(‘keyword’) 在源码中查找
browser.maximize_window() - 浏览器窗口最大化 窗口最大化

(b) 节点操作

节点操作 作用
node.send_keys(‘’) 在文本框填写内容
node.click() 点击
node.get_attribute(‘href/id/name’) 获取节点属性
node.text 获取节点文本

(C) 查找节点

(a) 单个节点

做一个展示:

有多种方式,深入的方法可以通过官方文档进行系统的学习。

1
2
3
4
5
6
7
8
# 根据name值查找
find_element_by_name('sunrisecai')
# 根据id值查找
find_element_by_id('sunrisecai')
# 根据xpath查找
find_element_by_xpath('sunrisecai')
# 根据CSS选择器查找
find_element_by_css_selector('sunrisecai')

等价:

find_element(),里面填写需要查找的节点即可。

1
2
3
4
5
6
7
8
9
10
from selenium.webdriver.common.by import By

# 根据name值查找
find_element(By.NAME,'sunrisecai')
# 根据id值查找
find_element(By.ID,'sunrisecai')
# 根据xpath查找
find_element(By.XPATH,'sunrisecai')
# 根据CSS选择器查找
find_element(By.CSS_SELECTOR,'sunrisecai')

(b) 多个节点

单个节点与多个节点的区别如下所示:

节点 区别 区别
单个节点 find_element find_element_by_xxx
多个节点 find_elements find_elements_by_xxx

可以看到基本上是一致的,不同点在于多个节点的element多了一个s,即elements。

(D) 模拟填写表单与点击

填写表单:

1
2
3
4
5
6
7
8
# 首先定位到文本框
text_box = browser.find_element_by_xpath('xxx')

# 清空文本框
text_box.clear()

# 输入文字
text_box.send_keys('xxx')

模拟点击:

1
2
# 点击搜索
browser.find_element_by_xpath('xxx').click()

二. 自动化测试案例

这里自动化测试运行了“合成大西瓜”游戏

(A) 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from selenium import webdriver
from time import sleep #sleep模块,让程序停止往下运行
from selenium.webdriver.common.action_chains import ActionChains #操作链
import random

wd = webdriver.Chrome(r'D:\chromedriver.exe')
wd.get('https://dushusir.com/xigua') #打开设定的网址
wd.implicitly_wait(5) #隐式等待

wd.set_window_rect(0,0,700,700) #设置浏览器大小
while(True):

#随机取位置
randomX = random.randint(20, 300)
randomY = random.randint(200, 300)

print('click')
# 设定点击位置
ActionChains(wd).move_by_offset(randomX, randomY).click().perform()

# move_by_offset会累计上一次的位置,点击完重置
ActionChains(wd).move_by_offset(-randomX, -randomY).perform()
sleep(1)

三. 爬取京东商城商品

(A) 代码

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import selenium.common.exceptions
import json
import csv
import time

class JdSpider():
def open_file(self):
self.fm = input('请输入文件保存格式(txt、json、csv):')
while self.fm!='txt' and self.fm!='json' and self.fm!='csv':
self.fm = input('输入错误,请重新输入文件保存格式(txt、json、csv):')
if self.fm=='txt' :
self.fd = open('Jd.txt','w',encoding='utf-8')
elif self.fm=='json' :
self.fd = open('Jd.json','w',encoding='utf-8')
elif self.fm=='csv' :
self.fd = open('Jd.csv','w',encoding='utf-8',newline='')

def open_browser(self):
self.browser = webdriver.Chrome(r'D:\chromedriver.exe')
self.browser.implicitly_wait(10)
self.wait = WebDriverWait(self.browser,10)

def init_variable(self):
self.data = zip()
self.isLast = False

def parse_page(self):
try:
skus = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//li[@class="gl-item"]')))
skus = [item.get_attribute('data-sku') for item in skus]
links = ['https://item.jd.com/{sku}.html'.format(sku=item) for item in skus]
prices = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//div[@class="gl-i-wrap"]/div[2]/strong/i')))
prices = [item.text for item in prices]
names = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//div[@class="gl-i-wrap"]/div[3]/a/em')))
names = [item.text for item in names]
comments = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//div[@class="gl-i-wrap"]/div[4]/strong')))
comments = [item.text for item in comments]
self.data = zip(links,prices,names,comments)
except selenium.common.exceptions.TimeoutException:
print('parse_page: TimeoutException')
self.parse_page()
except selenium.common.exceptions.StaleElementReferenceException:
print('parse_page: StaleElementReferenceException')
self.browser.refresh()

def turn_page(self):
try:
self.wait.until(EC.element_to_be_clickable((By.XPATH,'//a[@class="pn-next"]'))).click()
time.sleep(1)
self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")
time.sleep(2)
except selenium.common.exceptions.NoSuchElementException:
self.isLast = True
except selenium.common.exceptions.TimeoutException:
print('turn_page: TimeoutException')
self.turn_page()
except selenium.common.exceptions.StaleElementReferenceException:
print('turn_page: StaleElementReferenceException')
self.browser.refresh()

def write_to_file(self):
if self.fm == 'txt':
for item in self.data:
self.fd.write('----------------------------------------\n')
self.fd.write('link:' + str(item[0]) + '\n')
self.fd.write('price:' + str(item[1]) + '\n')
self.fd.write('name:' + str(item[2]) + '\n')
self.fd.write('comment:' + str(item[3]) + '\n')
if self.fm == 'json':
temp = ('link','price','name','comment')
for item in self.data:
json.dump(dict(zip(temp,item)),self.fd,ensure_ascii=False)
if self.fm == 'csv':
writer = csv.writer(self.fd)
for item in self.data:
writer.writerow(item)

def close_file(self):
self.fd.close()

def close_browser(self):
self.browser.quit()

def crawl(self):
self.open_file()
self.open_browser()
self.init_variable()
print('开始爬取')
self.browser.get('https://search.jd.com/Search?keyword=%E7%AC%94%E8%AE%B0%E6%9C%AC&enc=utf-8')
time.sleep(1)
self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")
time.sleep(2)
count = 0
while count<=3:
#while not self.isLast:
count += 1
print('正在爬取第 ' + str(count) + ' 页......')
self.parse_page()
self.write_to_file()
self.turn_page()
self.close_file()
self.close_browser()
print('结束爬取')

if __name__ == '__main__':
spider = JdSpider()
spider.crawl()

(B) 数据元素简介

我们需要解析每一个网页来获取我们需要的数据,具体包括(可以使用 Selenium 选择元素):

  • 商品 ID,用于构造链接地址
1
browser.find_elements_by_xpath('//li[@data-sku]')
  • 商品价格
1
browser.find_elements_by_xpath('//div[@class="gl-i-wrap"]/div[2]/strong/i')
  • 商品名称
1
browser.find_elements_by_xpath('//div[@class="gl-i-wrap"]/div[3]/a/em')
  • 评论人数
1
browser.find_elements_by_xpath('//div[@class="gl-i-wrap"]/div[4]/strong')

(C) Tips

代码有几个需要注意的地方,现在记录下来便于以后学习。

(1) self.fd = open('Jd.csv','w',encoding='utf-8',newline='')

再打开 csv 文件时,最好加上参数 newline=’’,否则我们写入的文件会出现空行,不利于后续的数据处理。

(2) self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")

在模拟浏览器向下拖动网页时,由于数据更新不及时,所以经常出现 StaleElementReferenceException 异常。一般来说有两种处理方法:
在完成操作后使用 time.sleep() 给浏览器充足的加载时间;捕获该异常进行相应的处理。

(3) skus = [item.get_attribute('data-sku') for item in skus]

在 Selenium 中使用 xpath 语法选取元素时,无法直接获取节点的属性值,而需要使用 get_attribute() 方法。

(4) 无头启动浏览器可以加快爬取速度,只需在启动浏览器时设置无头参数即可

1
2
3
opt = webdriver.chrome.options.Options()
opt.set_headless()
browser = webdriver.Chrome(chrome_options=opt)