作为爬虫公开的网页数据的学习,又能避开代理的问题,用selenium是一个很好的方法
本文讲的是爬虫淘宝网页商品的信息:标题,价格,销量,产地(省份/城市)
有的xpath很难获取,可以选择用chorm插件xpath helper
以下主要为代码部分,有selenium基础的应该很容易看懂
部分封装
def find_element(self, element):
"""
这里添加了一个over_time作为查找元素的超时次数,根据系统的实际情况设置OVER_TIME的大小
"""
for i in range(over_time):
try:
# return self.driver.find_element(*element)
return self.driver.find_element(By.XPATH, element)
except Exception as e:
print(e)
注意:
使用selenium时,爬取商品数据时,需要下拉滚动页面到底部,这样才能获取当前页所有的商品基本展示信息。
可能原因:商品页中商品展示使用Ajax动态请求,目的是为了可以在不重载网页的条件下,与服务器进行数据交互,更新页面的局部数据。(使用Ajax请求对于流量费的节省和阻碍网络爬虫都有帮助)
# 滑动页面到底部
def scroll_to_end(self):
for i in range(3):
self.driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(2)
# 点击下一页数据
def click_page(self, page):
try:
return self.driver.execute_script("arguments[0].click();", page)
except Exception as e:
print(e)
rand_num = random.randint(3, 6) # 设置随机等待的时间
time.sleep(rand_num)
goods_messege.py
def spider_goods(num_p):
goods_messeges = []
title_e = "//div[@class='Title--title--jCOPvpf']"
price_int = '//span[@class="Price--priceInt--ZlsSi_M"]'
price_float = '//span[@class="Price--priceFloat--h2RR0RK"]'
sale_real = '//span[@class="Price--realSales--FhTZc7U"]'
next_page = "/html[@class='ks-webkit537 ks-webkit ks-chrome120 ks-chrome']/body/div[@id='root']/div/div[3]/div[@class='PageContent--contentWrap--mep7AEm']/div[@class='LeftLay--leftWrap--xBQipVc']/div[@class='LeftLay--leftContent--AMmPNfB']/div[@class='Pagination--pgWrap--kfPsaVv']/div[@class='next-pagination next-medium next-normal']/div[@class='next-pagination-pages']/button[@class='next-btn next-medium next-btn-normal next-pagination-item next-next']/span[@class='next-btn-helper']"
for p in range(num_p):
obj_tit = baseClass.BaseClass().find_elements(title_e)
price_int_xl = baseClass.BaseClass().find_elements(price_int)
price_float_xl = baseClass.BaseClass().find_elements(price_float)
sale_real_xl = baseClass.BaseClass().find_elements(sale_real)
next_page_xl = baseClass.BaseClass().find_element(next_page)
print("第{}页数据有{}条:".format(p+1, len(obj_tit)))
# print(len(obj_tit))
for i in range(len(obj_tit)):
try:
title = obj_tit[i].text.replace(" ", "").strip() # 空格替换为空,头尾去掉空格
price1 = price_int_xl[i].text.replace(" ", "").strip()
price2 = price_float_xl[i].text.replace(" ", "").strip()
price = price1 + price2 # 整数部分加小数部分
sale = sale_real_xl[i].text.replace(" ", "").strip()
# 转化万为数字
if '万' in sale:
sale_n = sale.split("万")[0]
# print(sale_n)
sale = int(10000 * int(sale_n))
# print(sale)
else:
sale = int(sale.split("+")[0])
province = baseClass.BaseClass().find_element(
"//div[@class='Content--contentInner--QVTcU0M']/div[{}]/a[@class='Card--doubleCardWrapper--L2XFE73']/div[@class='Card--doubleCard--wznk5U4']/div[@class='Card--mainPicAndDesc--wvcDXaK']/div[@class='Price--priceWrapper--Q0Dn7pN ']/div[1]/span[@class='Price--procity--_7Vt3mX']".format(
i + 1)).text.replace(
" ", "").strip()
city_xl = baseClass.BaseClass().is_xpath_present(
"//div[@class='Content--contentInner--QVTcU0M']/div[{}]/a[@class='Card--doubleCardWrapper--L2XFE73']/div[@class='Card--doubleCard--wznk5U4']/div[@class='Card--mainPicAndDesc--wvcDXaK']/div[@class='Price--priceWrapper--Q0Dn7pN ']/div[2]/span[@class='Price--procity--_7Vt3mX']".format(
i + 1))
city = city_exist(i, city_xl)
if city == '':
city = province
goods_messeges.append([title, price, sale, province, city])
else:
goods_messeges.append([title, price, sale, province, city])
except Exception as e:
print(e)
if p < num_p-1:
print("点击下一页,至第{}页".format(p + 2))
baseClass.BaseClass().click_page(next_page_xl)
baseClass.BaseClass().scroll_to_end() # 滚动到底部
mdata = pd.DataFrame(goods_messeges, columns=['标题', '价格', '销量', '省', '市']) # 保存爬取信息
mdata.to_csv(execl_save, index=False) # 生成文档
测试用例:
# conding=utf-8
# @author: may
import unittest
import time
import allure
from config.globalparameter import key_messege, url
from src.common.driver_configure import GetDriver
# from src.common.logger import Logger
from src.common import baseClass
from src.pages import buttom_page
from goods import goods_messege
over_time = 3
# mylogger0 = Logger(logger='test_login').getlog() # 存放日志
@allure.feature('selenium自动化测试: 登录')
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""
测试前期准备条件!!!!!!!!!
"""
cls.driver = GetDriver().get_driver() # 实例化浏览器
baseClass.BaseClass().start(url)
baseClass.BaseClass().login_cookie() # 执行完第一登录后运行
time.sleep(3)
@classmethod
def tearDownClass(cls):
"""
每个测试用例都会执行一遍setUp()和tearDown(),所以一般把tearDown()中的浏览器退出改为刷新refresh()
如果添加了修饰器@classmethod,因为回只执行一次tearDown,所以可以直接退出浏览器quit()
"""
try:
pass
# getDriver().get_driver().quit()
except ConnectionRefusedError as e:
print(e)
# finally:
# self.assertEqual([], self.verificationErrors)
# @allure.story('登录测试')
# @allure.severity('blocker')
def test_0_search(self):
self.search_page0 = buttom_page.SearchPage()
print("进行搜索操作!!!")
self.search_page0.input_key(key_messege)
self.search_page0.search_b()
time.sleep(10)
self.search_page0.sales_rank()
time.sleep(2)
self.search_page0.choose_good()
time.sleep(2)
baseClass.BaseClass().scroll_to_end() # 滚动到底部
goods_messege.spider_goods(5) # 数据爬虫. 5表示爬虫 5 页的数据
# 存储登录的cookie
baseClass.BaseClass().save_logincookie()
time.sleep(10)
if __name__ == "__main__":
unittest.main()
运行结果数据:
优化:spider_goods() 方法中,路径如果稍微变的话,很容易就定位不到,可以用page_source获取网页源,还有就是保存到mysql中python淘宝网页爬虫数据保存到 csv和mysql(selenium)