得有两个月没记笔记了,懒是一方面,主要还是天天这部那局的需求不断,劳心费力呀,也没得啥时间;最近给业务部门做了一个简单的爬虫项目,使用的是selenium4,需求很简单,但到了桃宝,你东上,面对复杂的元素结构,有些数据还是蛮难取的,记录一下踩过的坑点:
1、防爬虫绕过
参考https://www.jianshu.com/p/368be2cc6ca1,这里的解决方案是加参和修改chromdriver.exe的驱动内字段:
option = ChromeOptions()
'''
规避检测参数
最好更改谷歌浏览器驱动这个值 $cdc_asdjflasutopfhvcZLmcfl(注意要等长)
不被检测出来的话,一般是不用滑块验证的
阿里的滑块通过率不高,比较麻烦
'''
option.add_argument("--disable-blink-features=AutomationControlled")
option.add_experimental_option('excludeSwitches', ['enable-automation'])
# 实例化浏览器对象
driver = webdriver.Chrome(service=Service(r'C:\Program Files\Google\Chrome\Application\chromedriver.exe'))
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
2、一些Selenium4的基本操作
推荐边看边做:https://www.byhy.net/
比如获取元素路径写法、显式(隐式)等待写法,都是和selenium3有所不同的:
driver.find_element(By.XPATH,'//a[@class = "h"]').click()
time.sleep(1)
#切换扫码登录 中间需要1-2步人工确认,所以等待15s
driver.find_element(By.XPATH,'//i[@class = "iconfont icon-qrcode"]').click()
time.sleep(15)
鼠标移动到元素上并悬停:
from selenium.webdriver.common.action_chains import ActionChains
ac = ActionChains(driver)
# 鼠标移动到 元素上
ac.move_to_element(
driver.find_element(By.XPATH, '//*[@id="J_PriceStart"]')
).perform()
time.sleep(5)
切换窗口:
# 切换窗口
driver.switch_to.window(driver.window_handles[-1])
time.sleep(1)
保存当前窗口句柄:
# mainWindow变量保存当前窗口的句柄
mainWindow = driver.current_window_handle
切换至新窗口并切换句柄,这样driver才能在新页面获取元素:
# WebDriver对象有window_handles 属性,这是一个列表对象, 里面包括了当前浏览器里面所有的窗口句柄。
# 所谓句柄,大家可以想象成对应网页窗口的一个ID
# 切换到新窗口操作 handle:窗口句柄
for handle in driver.window_handles:
# 先切换到该窗口
driver.switch_to.window(handle)
# 得到该窗口的标题栏字符串,判断是不是我们要操作的那个窗口
if href_text in driver.current_url:
# 如果是,那么这时候WebDriver对象就是对应的该该窗口,正好,跳出循环,
break
time.sleep(1)
返回老句柄:
# 通过前面保存的老窗口的句柄,自己切换到老窗口
driver.switch_to.window(mainWindow)
3、find_elements 和 find_element
后者好理解,就是找元素用的方法,前者一般在遍历列表、确认业务逻辑是否能翻页时用到,比如:
li_list = driver.find_elements(By.XPATH, '//div[@class="sf-content"]/div[@class="sf-item-list"]/ul/li')
time.sleep(1)
print(len(li_list))
if len(li_list) > 0:
for li in li_list:
title = li.find_element(By.XPATH, './a/div[1]/p').text
dq_price = li.find_element(By.XPATH, './a/div[2]/p[{}]'.format(dq_x)).text
xxx 其实确认翻页也不一定用确认该elements下元素个数来决定是否翻页,还是需要根据页面给的元素场景来,比如桃宝我就直接定位了某一翻页元素的值,从而确定是否需要翻页。
4、有时会报找不到元素的错误
改成如下方法:
# td.click()
#0610报找不到元素坐标错,好奇怪,不知道是不是102的chromedriver不稳定,改成下面方法
driver.execute_script("arguments[0].click()", td)
5、千人千面的详细信息数据获取
元素标签千奇百怪,相同标签在这位置,有的在那位置,有的甚至没有,给自动化起了很大的阻碍作用,为了做到相对统一我甚至:
狗看了狗都死了,最后算是做到了相对统一的结果。。。
6、表格记录
用到了openxyl,网上一搜一堆
主要就是创建sheet,写入表头,写入数据,追加sheet等,还是要基于自己的业务逻辑来写入(无非就是大段大段的条件判断、循环处理)
7、截图
直接扔网上的轮子:
def capture_full_screen(driver) :
def send(cmd, params):
resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
url = driver.command_executor._url + resource
body = json.dumps({"cmd":cmd, "params": params})
response = driver.command_executor._request("POST", url, body)
return response.get("value")
def evaluate(script):
response = send("Runtime.evaluate", {"returnByValue": True, "expression": script})
return response["result"]["value"]
metrics = evaluate(\
"({" + \
"width: Math.max(window.innerWidth, document.body.scrollWidth, document.documentElement.scrollWidth)|0," + \
"height: Math.max(innerHeight, document.body.scrollHeight, document.documentElement.scrollHeight)|0," + \
"deviceScaleFactor: window.devicePixelRatio || 1," + \
"mobile: typeof window.orientation !== 'undefined'" + \
"})")
send("Emulation.setDeviceMetricsOverride", metrics)
screenshot = send("Page.captureScreenshot", {"format": "png", "fromSurface": True})
send("Emulation.clearDeviceMetricsOverride", {})
return base64.b64decode(screenshot["data"])
8、取完数据后的表格美化(去空行)
wb = openpyxl.load_workbook(r'D:\\1.xlsx')
ws = wb.active
row = ws.max_row
column = ws.max_column
# 从大到小迭代
for x in range(row, 1, -1):
s = ws.cell(x, column).value
if s is None:
ws.delete_rows(x)
else:
pass
wb.save(r'D:\\1.xlsx')
ex、处理数据的正则、切片、随机数、写文件、替换等
stock_code = []
with open(r'D:\\2.txt', 'r') as f:
for item in f:
item = item[:-3].replace('.', '')
stock_code.append(item)
with open(r'D:\\' + status_text + '_' + re_title + '_' + serno + r'.html','wb') as f:
f.write(inner_html.encode('utf-8'))
import re
str = re.sub('[^\u4e00-\u9fa5]+','',confirm_content_price)
print(str)
print(str[:4]+':'+str[4:])
# 生成随机数
serno = str(random.randint(100000000, 999999999))
要涉及到业务逻辑,主要还是以Selenium为主,一些基础方法为辅,简单记录下备忘。
0706关于iframe的新坑
面对iframe的场景,需要在循环最后再切回原iframe,要不回原页面循环会报:element is not attached to the page document错误
需要在在开新窗口取数循环体的结构里做如下操作:
循环外先定位到对应的iframe:
iframe = driver.find_element(By.XPATH,'//*[@class = "ifr01"]')
driver.switch_to.frame(iframe)
进循环切新页面后,在关闭新页面并返回原句柄的同时,也需要切回下原iframe:
# 关闭当前页面
driver.close()
# 通过前面保存的老窗口的句柄,自己切换到老窗口
driver.switch_to.window(mainWindow)
time.sleep(1)
# 面对iframe的场景,需要在循环最后再切回原iframe,要不回原页面循环会报:
# element is not attached to the page document错误
iframe = driver.find_element(By.XPATH, '//*[@class = "ifr01"]')
driver.switch_to.frame(iframe)