如果异步加载页面,除了抓包,还可以模拟浏览器访问的方法,获取页面的内容。这是一个万金油的方法,唯一的方法是非常消耗资源和时间。
1. 启动浏览器
1.1. 浏览器驱动
模拟不同的浏览器需要调用不同的驱动。
支持以下几种种驱动的形式
webdriver.Firefox(firefox_profile=None, firefox_binary=None, timeout=30, capabilities=None, proxy=None, executable_path='wires')
webdriver.Chrome(executable_path='chromedriver', port=0, chrome_options=None, service_args=None, desired_capabilities=None, service_log_path=None)
webdriver.Ie
webdriver.Opera
webdriver.Phantomjs
webdriver.Safari
webdriver.Android
webdriver.BlackBerry
webdriver.WebKitGTK
webdriver.Remote(command_executor='http://127.0.0.1:4444/wd/hub', desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False)
最后一种方法是自定义模拟调试,是其他所有模拟调试的基类,会返回一个
WebDriver
对象要使用这个方法,服务端需要先下载jar包
Selenium-Server
,然后运行java -jar selenium-server-standalone-3.3.1.jar
启用服务,客户端才能进行远程调试。
下面以模拟Chrome浏览器为例说明相应的方法,除了设置浏览器启动参数不太一样(这个可以自行查阅官方的api),其他对应的方法都差不多
1.2. 设置浏览器驱动
手动配置版
在Chrome浏览器中输入 chrome://version/
,获取chrome浏览器的版本号,可以选择官网(国内正常情况下一般打不开)或淘宝镜像下载对应版本的Chromedriver,然后程序中设置该驱动的路径,如果驱动在环境变量中(一般直接放在程序运行文件夹下即可),则可以不用进行设置
特别地,mac中不可以直接指定驱动的路径,需要将其加入环境变量中,最快速的方法是使用下面语句将其加入到
/usr/local/bin/
文件夹中mv ~/Downloads/chromedriver /usr/local/bin/
驱动安装完毕后,还需在设置安全中允许启动
另外,浏览器驱动不一定需要下载,可以在浏览器安装目录下找到对应的驱动文件。
自动配置版
安装 webdriver-manager
,只需如下操作
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(ChromeDriverManager().install())
这个包可以自动帮我们下载并配置好浏览器的驱动
这个包的好处是免去环境的变量的配置,更重要的一点是,本地的浏览器(点名chrome)会自动更新,这个包可以免去每次自动更细都要重新下载一遍驱动的麻烦
1.3. 设置浏览器启动参数
options = webdriver.ChromeOptions()
# 显式设置为True表示静默启动,即不显示浏览器
options.headless
# 添加扩展项
options.add_extension(extension_path)
options.add_encoded_extension(extension_Base64)
options.extensions # 查看已经添加的扩展项,以base64列表形式给出
# 显式设置调试地址
options.debugger_address
# 添加浏览器启动参数
options.add_argument(argument)
options.arguments
# chrome 本地二进制文件位置
options.binary_location
# 添加实验性质的设置参数
# 暂时不太理解这个参数的含义
options.add_experimental_option(name, value)
options.experimental_options
# 貌似是java使用到的方法,暂时不太理解这个参数的含义
options.set_capability(name, value)
options.to_capabilities()
特别地,可以命令行中使用
chromedriver --help
来获取浏览器所有的启动参数,例如options.add_argument("'--proxy-server={}".format(proxy))
1.4. 页面访问
貌似只能使用get方法进行请求
from selenium import webdriver
options = webdriver.ChromeOptions()
driver = webdriver.Chrome(chrome_options=options)
driver.get(url)
设置超时
driver.set_page_load_timeout(timeout) # 连接超时
driver.set_script_timeout(timeout) # 加载超时
使用上面这种方法在网页超时后,driver
就会死掉,需要重新启动浏览器,即 quit
掉原来的 driver
,重新创建一个 driver
对象,比较蛋疼,但目前没有找到更加好的办法,只能将就着用。
特别地,还可以使用隐式等待的方式:
driver.implicitly_wait(timeout)
,但我试了一下,这种方法貌似并不奏效,而且也并不推荐使用这种方式,因为隐式等待这种方式的可控性太低了。
2. 元素定位
提供下列方法进行元素定位
- find_element_by_tag_name
- find_element_by_class_name
- find_element_by_name
- find_element_by_link_text
- find_element_by_partial_link_text
- find_element_by_id
- find_element_by_xpath
- find_element_by_css_selector
特别地,还可以使用一个基类方法 find_element
进行定位,具体用法如下
from selenium.webdriver.common.by import By
element = driver.find_element(By.XPATH,'//button[text()="Some Text"]')
By
类的可用属性如下:
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag_name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
上面方法返回的是一个 WebElement
对象,特别地,如果想要返回所有定位成功的元素,使用 find_elements_*
方法,返回的是一个list
对象
WebDriver
对象和 WebElement
对象都具有上述元素定位的方法。
特别地,如果页面是异步加载,网址一连接成功马上定位会失败报错,一般有以下两种情况
一种是像AJAX类的异步加载,需要等待其加载成功
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait wait = WebDriverWait(driver, timeout) element = wait.until(EC.element_to_be_clickable((By.ID,'someid'))) element = wait.until(lambda x: x.find_element_by_class_name(class_name))
一种是需要到达指定位置才会被加载,一般是滚到浏览器底部才会继续加载
js = 'window.scrollTo(0,document.body.scrollHeight);' driver.execute_script(js)
3. 获取信息
3.1. 获取网页信息
driver.page_source # 网页html
driver.current_url # 网页url
driver.title # 网页标题
# 网页cookies
driver.get_cookies()
driver.get_cookie(name)
driver.add_cookie(cookie_dict)
driver.delete_all_cookies()
driver.delete_cookie(name)
# 调试日志
# log_type: browser, driver, client, server
driver.get_log(log_type)
# 网页截图
driver.get_screenshot_as_base64()
driver.get_screenshot_as_png()
driver.get_screenshot_as_file(filename) # 等价于 driver.save_screenshot(filename)
# 获取当前浏览器大小
# 左上为坐标原点,以像素为单位,windowHandle参数可以忽略
driver.get_window_position(windowHandle='current') # 左上顶点的坐标
driver.get_window_size(windowHandle='current') # 长和宽
driver.get_window_rect() # 上面两个的综合
3.2. 获取标签信息
# 标签属性
element.tag_name
element.text
element.id
# 标签性质
element.is_displayed() # 隐藏
element.is_enabled() # 可用
element.is_selected() # 可被选中
element.get_attribute(name) # 标签属性
element.get_property(name) # 标签性质
element.value_of_css_property(property_name) # css标签性质
# 临接标签节点
element.parent
# 标签截图
element.screenshot_as_base64
element.screenshot_as_png
element.screenshot(filename)
# 标签大小
element.location # 左上顶点的坐标
element.location_once_scrolled_into_view # 只返回可见标签的坐标
element.rect # 长和宽
element.size # 上面两个的综合
4. 模拟操作
4.1. 浏览器操作方法
driver.back()
driver.forward()
driver.refresh()
driver.close()
driver.execute_script(script, *args) # 当前页面下运行js
# 浏览器大小调整
driver.fullscreen_window()
driver.maximize_window()
driver.minimize_window()
driver.set_window_rect(x=None, y=None, width=None, height=None)
driver.set_window_size(width, height, windowHandle='current')
4.2. 元素操作方法
element.send_keys(*value) # 插入文本
element.clear() # 清除文本
element.submit() # 提交表单
element.click() # 点击
4.3. 更加复杂的操作
selenium
定义了 ActionChains
类提供更多复杂的操作
from selenium.webdriver.common.action_chains import ActionChains
actions = ActionChains(driver)
# 鼠标点击方法
actions.click(on_element=None) # 单击
actions.click_and_hold(on_element=None) # 单击并按住
actions.release(on_element=None) # 释放鼠标
actions.context_click(on_element=None) # 右击
actions.double_click(on_element=None) # 双击
actions.drag_and_drop(source, target) # 点击 source 并按住移动到 target 处放开,输入的都是 element
actions.drag_and_drop_by_offset(source, xoffset, yoffset) # 跟上面的差不多,不过 target 输入的是坐标
actions.move_by_offset(xoffset, yoffset) # 鼠标移动到对应的坐标
actions.move_to_element(to_element) # 鼠标移动到对应的标签
actions.move_to_element_with_offset(to_element, xoffset, yoffset)
# 键盘操作方法
actions.key_down(value, element=None) # 按住键盘不放开
actions.key_up(value, element=None) # 释放键盘按键
actions.send_keys(*keys_to_send) # 当前位置插入文本
actions.send_keys_to_element(element, *keys_to_send) # 指定标签插入文本
# 具体执行方法
actions.pause(seconds)
actions.perform()
actions.reset_actions()
特别地,上面指令执行后不会立刻执行,而是进入一个队列,只有执行
actions.perform()
指令后才会排队进行操作,且每次执行完后队列不会清空,需要手动reset_actions()
清空另外,键盘操作中如果需要键入一些特殊的键,例如回车,需要引入
Keys
类from selenium.webdriver.common.keys import Keys actions.send_keys(Keys.ENTER)
4.4. 警告框操作
selenium
定义了 Alert
类提供对警告框的操作,如果不先对警告框进行操作,而进行其他浏览器的操作,程序会报错。
from selenium.webdriver.common.alert import Alert
alert = Alert(driver)
alert.accept()
alert.dismiss()
alert.send_keys(keysToSend)
alert.text
4.5. 页面切换操作
driver.current_window_handle # 获取窗口的当前句柄
handles = driver.window_handles # 获取窗口的所有句柄
# 切换窗口页
driver.switch_to.window(handles[1])
# 切换框架
driver.switch_to.frame('frame_name')
driver.switch_to.frame(element.id)
driver.switch_to.frame(element)
driver.switch_to.default_content() # 切至主框架
driver.switch_to.parent_frame() # 切至父框架
# 切换至警告框
# 会返回一个 `Alert` 对象
alert = driver.switch_to.alert()
上面的切换方法 switc_to.*
等价于 switch_to_*
,但 selenium
不建议使用后者,并计划在日后的更新中将其去掉。
frame标签有frameset、frame、iframe三种,frameset跟其他普通标签没有区别,不会影响到正常的定位,而frame与iframe相当于一个新的页面,不可以直接定位,其中,frame是整个页面的框架,iframe是内嵌的页面元素。frame与iframe在selenium中的切换操作是通用的。特别地,有多级框架时,框架必须一级一级往下切换,不能直接越级切换。
5. references
https://selenium-python.readthedocs.io/
https://python-selenium-zh.readthedocs.io/zh_CN/latest/