英文教程网址: 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()
解释:
-
find_elements_by_*
(*可以是name,id,xpath,tag_name等)用于定位元素。返回结果为满足条件的element列表。 - 这里使用的是
find_element_by_*
是单数的,返回值为element。如果没有结果会触发NoSuchElementException
。
导航
使用get命令来获取页面
driver.get(url)
页面互动
- 首先找到元素
- 使用
send_keys()
命令进行输入,使用clear()
命令来清除文本框已存在的内容。 - 使用
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.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_element
和find_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)
对页面进行解析。
如果需要操作页面,可以操作完成后再解析。