无头浏览器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:
<a href="somewhere.html" target="windowName">Click here to open a new window</a>

另外,也可以将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)对页面进行解析。
如果需要操作页面,可以操作完成后再解析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容