Selenium+phantomjs模拟网页自动化操作

N久之前使用这对好伙伴实现支持抓取ajax数据的爬虫,最近有个需求是完全模拟某网站的购物操作,结果这个网站比较奇葩,对于表单中某些文本框、复选框等修改后会触发post操作并更改页面内容,而且这些控件之间还会互相影响,直接使用post方式的话比较困难,于是再次祭出神器: selenium 和phantomjs,此外还可以尝试selenium-ide

这里phantomjs2.0版本并没提供编译好的通用Linux版,所以本文依然使用1.9版,但在2.0版上测试通过。

# coding=utf-8
from selenium import webdriver
def main():
    dcap = dict(DesiredCapabilities.PHANTOMJS)
    dcap["phantomjs.page.settings.resourceTimeout"] = 15  # 超时
    dcap["phantomjs.page.settings.loadImages"] = False #不加载图片
    dcap["phantomjs.page.settings.userAgent"] = (
         "Mozilla/5.0 (X11; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0") #伪装ua
    service_args = [
        '--ignore-ssl-errors=true',
        '--cookies-file=test.cookies',
        '--disk-cache=true',
        '--local-to-remote-url-access=true',
        '--proxy=127.0.0.1:7070',
        '--proxy-type=socks5',
        #'--proxy-type=http',
        '--proxy-auth=xx:xx',
        '--web-security=false',
    ]
    driver = webdriver.PhantomJS(
        executable_path='./phantomjs',
        desired_capabilities=dcap,
    #    service_args=service_args,
    )

如果需要使用代理或有特殊需求,把service_args这行代码取消注释即可。

对于正常的浏览网页或者网上购物来说,最基本的操作不外乎点击按钮、单选框、复选框,填写文本框,选择下拉菜单,示例如下:

driver.get(url) #打开网页
elem = driver.find_element_by_xpath(xpath路径字符串) #找到要操作的元素
elem.click() #点击事件
elem.send_keys("some text") #填写文本框
elem.clear() #清空文本框

至于xpath是我最近接触的第二个觉得可归为"神器",在使用lxml解析网页时这个语法也是十分有用,这里不细说,有时间专写一个关于xpath的文章。那么万一不会xpath怎么办呢?还有下面的几种方式获取这个元素:

  • find_element_by_id
  • find_element_by_name
  • find_element_by_xpath
  • find_element_by_link_text
  • find_element_by_partial_link_text
  • find_element_by_tag_name
  • find_element_by_class_name
  • find_element_by_css_selector

至于用法都是一样的,具体看上面的文档链接即可。

类似按钮、链接、单选框、复选框这种元素直接click即可,那么下拉菜单怎么办呢?作者提供了一个select类来简化操作:

from selenium.webdriver.support.ui import Select
select = Select(driver.find_element_by_name('name'))
select.select_by_index(index)
select.select_by_visible_text("text")
select.select_by_value(value)

如果想得到这个下拉菜单的值,则可以使用select.all_selected_options得到一个列表。

如果页面中某些元素是ajax或js动态生成,并不是页面加载就存在的,那么我们就需要等待这些元素出现,官网示例如下:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

上面的代码将在10秒内每隔0.5秒去寻找id为mydynamicelement的元素,by语法支持的方法也有很多,比如xpath,name,link_text等,具体请看文档。

这里多提一句,虽然最终在服务器上是没有图形界面的,但开发时可以使用webdriver.Firefox()来看代码逻辑等是否正确,这会启动一个真实的火狐浏览器进行操作,如果没问题再把引擎换成phantomjs。如果使用火狐的话,使用代理则变成:

myProxy = "http://name:pswd@x.x.x.x:xxx"
proxy = Proxy({
   'proxyType': ProxyType.MANUAL,
   'httpProxy': myProxy,
   'ftpProxy': myProxy,
   'sslProxy': myProxy,
   'noProxy': ''  # set this value as desired
})
driver = webdriver.Firefox(proxy=proxy)

还有可能出现一种情况,就像我遇到的,每次修改文本框时都会重新加载页面某些元素,注意这里和上面说的“等待元素生成不同”,因为这个元素已经存在。所以有些时候需要使用time.sleep()函数等待,并且重新获取后再进行操作。

还有可能遇到的情况就是由于某些原因click事件不好使,或者某些隐藏元素只有在鼠标放在上面时才会显示出来,这时候可以采用下面的方法:

menu = driver.find_element_by_css_selector(".nav")
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()

那么,如果得到某个元素后,需要得到某个属性怎么办呢?如何得到当前url以及页面源码?可以使用

elem.get_attribute("value") # 取得属性值
driver.current_url # 当前url
driver.page_source # 当前页面源码

最后再说一种“大杀器”:

driver.execute_script("return navigator.userAgent")

看名字也知道这是干嘛的,没错,就是执行自己写的js代码。比如上面写的就是获取当前浏览器的ua信息,至于这个怎么用就看具体情况了。