常用python网络爬虫框架(二)--selenium


如果异步加载页面,除了抓包,还可以模拟浏览器访问的方法,获取页面的内容。这是一个万金油的方法,唯一的方法是非常消耗资源和时间。

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/


评论
  目录