无头浏览器Selenium

英文教程网址: https://selenium-python.readthedocs.io/installation.html

安装

pip install selenium

配置

驱动:Selenium需要驱动来和相关浏览器互动。例如,firefox需要geckodriver。这个需要提前安装好。

linux:下载好放到PATH里面,例如/usr/bin等中。

入门

一个简单的示例:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get("http://www.python.org")
assert "Python" in driver.title
elem = driver.find_element_by_name("q")
elem.clear()
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
assert "No results found." not in driver.page_source
# 获取页面html代码
driver.page_source
driver.close()

解释:

  1. find_elements_by_*(*可以是name,id,xpath,tag_name等)用于定位元素。返回结果为满足条件的element列表。
  2. 这里使用的是find_element_by_*是单数的,返回值为element。如果没有结果会触发NoSuchElementException

导航

使用get命令来获取页面
driver.get(url)

页面互动

  1. 首先找到元素
  2. 使用send_keys()命令进行输入,使用clear()命令来清除文本框已存在的内容。
  3. 使用click()命令来进行点击
  1. 对于复选框(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.deselect_all()
# 获取所有以选择的选项
all_selected_options = select.all_selected_options
# 获取所有可选的选项
options = select.options

发送箭头:send_keys(Keys.ARROW_DOWN)

定位元素

  • 用于发现单个元素:find_element_by_*
  • 用于发现多个元素:find_elements_by_*
    以上*号可以:
    id,name,xpath,link_text,partial_link_text,tag_name,class_name,css_selector

除了以上方法,还有两个private mehod也很有用:find_elementfind_elements

from selenium.webdriver.common.by import By

driver.find_element(By.XPATH, '//button[text()="Some text"]')
driver.find_elements(By.XPATH, '//button')

使用xpath定位元素时,建议先利用其他方法找到最近的元素,然后使用相对路径定位。
这种方法不易出错。

在窗口或frame之间切换

现代页面可能不止包含一个窗口或frame,在窗口间切换:
driver.switch_to_windon("windownName")

那么如何知道windowName呢,可以看一下打开这个窗口的javascript或者link:
Click here to open a new window

另外,也可以将window handle传递给switch_to_window()函数,例如:

for handle in driver.window_handles:
    driver.switch_to_window(handle)

同样的方法可以切换frame
driver.switch_to_frame("frameName")
也可以切换到子frame
driver.switch_to_frame("frameName.0.child")
这个语句切换到frameName的第一个子frame的名为child的子frame。
如果要切换会主frame,
driver.swith_to_default_content()

打开弹出窗口

alert = driver.switch_to_alert()

Cookies

driver.get("http://www.example.com")

# Now set the cookie. This one's valid for the entire domain
cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
driver.add_cookie(cookie)

# And now output all the available cookies for the current URL
driver.get_cookies()

等待

现在大部分网页采用了AJAX技术,网页加载时,不同的内容加载时间不同。这使得查找元素变得困难,如果没有加载上,查找某个元素会引起ElementNotVisibleException。使用等待能够解决这个问题。

分为两种,显式和隐式。显式等待:当某个条件发生后才继续进行。
隐式等待:等待固定的时间。

显式等待

需要WebDriverWait和ExpectedCondition相结合来操作。
示例代码:

from selenium import webdriver
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()

driver 会等待10秒钟,直到发现ID为myDynamicElement的元素,否则抛出TimeoutException异常。

expected condition

常见的条件:

  • title_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, 'someid')))

自己创建条件

如果上面的不满足你,也可以自己写一个类。只要__call__函数在不满足条件时,返回False即可。

  """An expectation for checking that an element has a particular css class.

  locator - used to find the element
  returns the WebElement once it has the particular css class
  """
  def __init__(self, locator, css_class):
    self.locator = locator
    self.css_class = css_class

  def __call__(self, driver):
    element = driver.find_element(*self.locator)   # Finding the referenced element
    if self.css_class in element.get_attribute("class"):
        return element
    else:
        return False

# Wait until an element with id='myNewInput' has class 'myCSSClass'
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))

隐式

一旦设置之后,在WebDriver 对象的整个生命周期内都有效。
隐形等待是设置了一个最长等待时间,如果在规定时间内网页加载完成,则执行下一步,否则一直等到时间截止,然后执行下一步。注意这里有一个弊端,那就是程序会一直等待整个页面加载完成,也就是一般情况下你看到浏览器标签栏那个小圈不再转,才会执行下一步,但有时候页面想要的元素早就在加载完成了,但是因为个别js之类的东西特别慢,我仍得等到页面全部完成才能执行下一步,我想等我要的元素出来之后就下一步怎么办?有办法,这就要看selenium提供的另一种等待方式——显性等待wait了。

这里需要注意,如果页面加载很快,那么就不等待了。

from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")
来源: http://selenium-python.readthedocs.io/waits.html

异常

引入异常:

from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException

执行js代码

在爬取网页时,有的网页使用了Ajax技术,只有当一定条件下(例如页面拖动了底部)才会加载,这种方法可以对局部页面进行加载而不需要重新加载整个页面。例如微博,地图等都使用了这种技术。
为了获取这些代码,可以使用以下js脚本:在下拉后一般需要等待一段时间

try:
        js = "window.scrollTo(0,document.body.scrollHeight)"
        driver.execute_script(js)
except WebDriverException:
        print("页面下拉失败")
time.sleep(2)

Scrapy-Selenium

结合scarpy和Selenium的神器
安装:pip install scrapy-selenium

配置:

需要在scrapy的setting中进行配置:

from shutil import which

SELENIUM_DRIVER_NAME = 'firefox'
SELENIUM_DRIVER_EXECUTABLE_PATH = which('geckodriver')
SELENIUM_DRIVER_ARGUMENTS=['-headless']  # '--headless' if using chrome instead of firefox

然后将中间件加入下载中间件:

DOWNLOADER_MIDDLEWARES = {
    'scrapy_selenium.SeleniumMiddleware': 800
}

用法

使用scrapy_selenium.SeleniumRequest代替scrapy内置的request

from scrapy_selenium import SeleniumRequest

yield SeleniumRequest(url=url, callback=self.parse_result)

这样,request就被selenium接管,在request的meta中多了一个driver key,保存了selenium driver。

def parse_result(self, response):
    print(response.request.meta['driver'].title)

Response的selector操作仍然可以使用,但是应用对象包含了Selenium driver返回的html

def parse_result(self, response):
    print(response.selector.xpath('//title/@text'))

相关参数

wait_time, wait_until

如果有这两个参数,selenium将在返回Response给spider之前执行显示等待

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

yield SeleniumRequest(
    url=url,
    callback=self.parse_result,
    wait_time=10,
    wait_until=EC.element_to_be_clickable((By.ID, 'someid'))
)

screenshot

截图,并返回到response的meta中

yield SeleniumRequest(
    url=url,
    callback=self.parse_result,
    screenshot=True
)

def parse_result(self, response):
    with open('image.png', 'wb') as image_file:
        image_file.write(response.meta['screenshot'])

script

如果出现,则会执行javascript

yield SeleniumRequest(
    url=url,
    callback=self.parse_result,
    script='window.scrollTo(0, document.body.scrollHeight);',
)

Scrapy 和Selenium配合使用

如果想要使用Scrapy的Selector读取Selenium的结果可以这样

from scrapy.contrib.spiders import CrawlSpider
from scrapy.http import Request
from scrapy import Selector

from selenium import webdriver

class MachineSpider(CrawlSpider):
    name = 'nc-spider'
    allowed_domains = ['ncservice.com']

    def start_requests(self):
        yield Request('http://www.ncservice.com/en/second-hand-milling-machines',
                      callback=self.parsencmilllist)

    def parsencmilllist(self, response):
        driver = webdriver.Firefox()

        driver.get(response.url)
        driver.find_element_by_id("mas-resultados-fresadoras").click()

        sel = Selector(text=driver.page_source)
        driver.quit()

        links = sel.xpath('//div[@id="resultados"]//a/@href').extract()
        for link in links:
            yield Request(link,
                          meta={'type': 'Milling'},
                          callback=self.parsencmachine)

    def parsencmachine(self, response):
        print response.url

可以看到,这里使用了Selector(text=driver.page_source)对页面进行解析。
如果需要操作页面,可以操作完成后再解析。

你可能感兴趣的:(无头浏览器Selenium)