0


python爬虫——selenium


一、背景

1.1、selenium的发展

    Selenium最初是由Jason Huggins测试工程师为减少手工测试量做的基于JavaScript语言的代码库(这时是通过js注入的方式操作),之后不断的发展完善并合并了WebDrive项目,使之可以直接调用浏览器和操作系统内置的方法,解决了JavaScript环境的沙盒限制,发展成了现在我们经常使用的Selenium WebDriver。现在的selenium就像百度百科说的一样,Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。

1.2、在爬虫中的应用

  在爬取数据的时候,我们通常可以使用requests库,但是如果遇到加密的网站,就需要js逆向,相当复杂。但是我们还有一种方法,那就是使用自动化工具,实现可见即可爬,完成JavaScript动态渲染页面。

1.3selenium执行原理图

1.4、WebDriver,与WebElement

   **WebDriver**的核心是是一个浏览器驱动程序,他负责启动浏览器,并将控制权交个程序,同时还提供了API,直接使用并控制浏览器的内置对象,可以使用它来模拟用户的行为,如点击提交,填写文本框,键入网址等。

   **WebElement**表示 DOM 元素。 通常,所有与指定元素交互的操作都将通过此接口执行。如:如点击提交,填写文本框,键入网址,还包括获取文本属性等,这是不是很熟悉,原来WebElement属于WebDrvierAPI;它可以对指定网页元素进行操作。

** 总结**

WebElement属于WebDriver API,通常使用于对指定可见元素进行操作,一般的实现如下:通过WebDriverAPI提供的方法(如:find_element())进行元素定位并返回WebElement对象,再调用此对象对指定元素进行操作。

二、准备

2.1、下载驱动

查看浏览器版本,下载对应驱动,相关驱动的下载链接如下

Chrome驱动:chromedriver.storage.googleapis.com/index.html

Firefox驱动:Releases · mozilla/geckodriver (github.com)

Edge驱动:Microsoft Edge WebDriver - Microsoft Edge Developer

2.2、安装Selenium库

pip install selenium

2.3、简单使用

​
from selenium import webdriver
import time
driver=webdriver.Chrome('./chromedriver.exe')#初始化WebDriver对象
driver.get('https://www.baidu.com')#访问百度
time.sleep(20)

​

Chrome的参数有很多,在这里我们初始化时传入的是executable_path,代表驱动的位置

驱动文件我放在了与代码同级目录下

三、实用操作

3.1、查找节点

3.1.1、查找元素在网页中的位置(网址为www.baidu.com,代码中的注释很详细)

3.1.2、实现方法

方法作用参数说明find_element得到首个被匹配的节点
By参数——By.ID通过标签ID属性匹配,By.CSS_SELECTOR,使用CSS选择器进行定位匹配,By.Name通过属性名进行匹配

Value参数——匹配的属性值
find_elements得到所有被匹配的节点,返回值为WebElement元素列表同上

3.1.3、实现代码

from selenium import webdriver
from  selenium.webdriver.common.by import By
import time
driver=webdriver.Chrome('./chromedriver.exe')
driver.get('http://www.baidu.com')
#css选择器进行筛选
#如果有多个元素被匹配,find_element只会获取第一个
result=driver.find_element(by=By.CSS_SELECTOR,value='.mnav.c-font-normal.c-color-t')
print(result,'\n','---------------')
#匹配多个find_elements
result_list=driver.find_elements(by=By.CSS_SELECTOR,value='.mnav.c-font-normal.c-color-t')
for item in result_list:
    print(item)
print('---------------')
result_byid=driver.find_element(by=By.NAME,value='tj_more')
print(result_byid)
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="bd760d4c-a045-4316-b398-7cb4ae7b6f6e")> 
 ---------------
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="bd760d4c-a045-4316-b398-7cb4ae7b6f6e")>
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="4c25b06b-8dd5-4da7-b362-43965f389102")>
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="43406d63-edea-4a7b-b2d2-307a7ad16714")>
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="c3434610-4579-4f43-9d42-42837dc8b151")>
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="fb6d11ff-f87b-4993-bad0-b35876e85c46")>
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="d8388d32-f732-488b-8b15-92a003c2a217")>
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="e8b99605-50d0-4679-9ef6-4a708d4d8ec8")>
---------------
<selenium.webdriver.remote.webelement.WebElement (session="695c760a0bf340402875429f56903178", element="e448b794-6c02-4683-8225-25a2d647d28d")>

3.2、获取节点属性,文本

driver=webdriver.Chrome('./chromedriver.exe')
driver.get('http://www.baidu.com')
result=driver.find_element(by=By.CSS_SELECTOR,value='.mnav.c-font-normal.c-color-t')
print(result.text,result.get_attribute('href'),'\n','------------------------------------')
result_list=driver.find_elements(by=By.CSS_SELECTOR,value='.mnav.c-font-normal.c-color-t')
for item in result_list:
    print(item.text,item.get_attribute('href'))
print('---------------------------------')
#webdriver只与可见元素交互,如果该元素被隐藏,将不会被获取文本值,但是属性仍可获取到,可通过is_displayed()进行查看
result_byid=driver.find_element(by=By.NAME,value='tj_more')
print({'text':result_byid.text,'href':result_byid.get_attribute('href')})
print(result_byid.is_displayed())
新闻 http://news.baidu.com/ 
 ------------------------------------
新闻 http://news.baidu.com/
hao123 https://www.hao123.com/?src=from_pc
地图 http://map.baidu.com/
贴吧 http://tieba.baidu.com/
视频 https://haokan.baidu.com/?sfrom=baidu-top
图片 http://image.baidu.com/
网盘 https://pan.baidu.com/?from=1026962h
---------------------------------
{'text': '', 'href': 'http://www.baidu.com/more/'}
False

3.3、节点交互

3.3.1、实现代码

from selenium import webdriver
import time
driver=webdriver.Chrome('./chromedriver.exe')
driver.get('https://www.baidu.com')
web_element=driver.find_element(By.CSS_SELECTOR,'.s_ipt')
web_element.send_keys('张三')
driver.find_element(By.CSS_SELECTOR,'[id="su"]').click()
time.sleep(100)

3.3.2、实现效果

3.3.3、常用交互操作

操作含义click()点击按钮sendkeys()输入到单选框clear()清空输入框

3.4、显示等待

3.4.1、等待页面元素

3.4.2、实现代码

from  selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
driver=webdriver.Chrome('./chromedriver.exe')
driver.get('http://www.baidu.com')
wait=WebDriverWait(driver,10)#传入webdriver对象,设置等待时间
#等待节点,返回值是WebElement
result_waitelement=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'.mnav.c-font-normal.c-color-t')))
#等待节点并判断某个节点是否满足条件,返回值是bool类型
result_waittext=wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, 'mnav'), '新闻'))#等待节点包含的文本值
result_waitattribute=wait.until(EC.text_to_be_present_in_element_attribute((By.CLASS_NAME,'mnav'),'href','http://'))#等待节点的属性值
print(result_waitelement.text,result_waittext,result_waitattribute)
新闻 True True

3.4.3、等待条件

含义返回类型title_is判断title是否出现booltitle_contains判断title页面标题是否包含某些字符boolurl_contains判断当前url是否包含某个urlboolurl_matches判断当前url是否符合某种格式boolpresence_of_element_located判断某个元素是否被加载到了dom树里,但是并不代表这个元素可见

WebElement

presence_of_all_elements_located判断至少有一个元素存在于dom树中,返回所有定位到的元素

List[WebElement]

visibility_of_all_elements_located判断是否所有元素都在页面中可见boolvisibility_of_element_located某个节点可见booltext_to_be_present_in_element判断指定的节点文本值中是否包含了预期的字符串booltext_to_be_present_in_element_value判断指定的节点值中是否包含了预期的字符串booltext_to_be_present_in_element_attribute判断指定节点值中指定属性是否包含预期字符串boolalert_is_present是否出现警告提示框boolelement_to_be_clickable按bool

3.5、Cookie

3.5.1、获取cookies

driver=webdriver.Chrome('./chromedriver.exe')
driver.get('https://login.taobao.com/member/login.jhtml')
time.sleep(40)#手动登录获取cookies
cookies=driver.get_cookies()
print(cookies,type(cookies))
with open('cookies.txt','w',encoding='utf-8') as f:
        json.dump(cookies,f)

2.5.2、携带cookies

driver=webdriver.Chrome('chromedriver.exe')
driver.get('https://www.taobao.com/')
with open(r'./cookies.txt','r',encoding='utf-8') as f:
   cookie_data=json.load(f)
   for item in cookie_data:
       print(item)
       driver.add_cookie(item)
driver.get('https://www.taobao.com/')
time.sleep(30)

3.5.3、实现效果

3.6、截屏

3.6.1、WebDriver对象实现截屏方法

方法作用返回值get_screenshot_as_png()获取当前窗口的屏幕截图作为二进制数据。byteget_screenshot_as_file()将当前窗口的屏幕截图保存到 PNG 图像文件。boolget_screenshot_as_base64获取当前窗口的屏幕截图作为 base64 编码字符串String

3.6.2、WebElement对象实现截屏方法

方法作用返回值screenshot_as_png获取当前窗口的屏幕截图作为二进制数据。bytescreenshot()将当前窗口的屏幕截图保存到 PNG 图像文件。boolscreenshot_as_base64获取当前窗口的屏幕截图作为 base64 编码字符串String

3.6.3、实现代码

driver=webdriver.Chrome('./chromedriver.exe')
driver.get('https://www.baidu.com')
driver.get_screenshot_as_file('./screenshot_png/1.png')#默认存储位置在当前同级目录下
driver.find_element(By.ID,'lg').screenshot('./screenshot_png/2.png')#截取选定的节点图片

3.6.4、实现效果

3.7、反检测

3.7.1、适用场景

很多网站防止被恶意爬虫爬取,对selenium进行检测

3.7.2、相关代码

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ChromeOptions
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from  selenium.webdriver.support import expected_conditions as EC
option=ChromeOptions()
#设置无头模式
# option.add_argument('--headless')
#反屏蔽处理
#以开发者模式启动调试chrome,可以去掉提示受到自动软件控制,隐藏提示条
option.add_experimental_option('excludeSwitches',['enable-automation'])
option.add_experimental_option('useAutomationExtension',False)#去掉提示以开发者模式调用,自动拓展信息

driver = webdriver.Chrome('chromedriver.exe',options=option)
# 将webdriver属性制为空
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument',{
    'source':'Object.defineProperty(navigator,"webdriver",{get:()=>undefined})'
})

3.8、设置无头

from selenium import webdriver
from selenium.webdriver import ChromeOptions
#设置无头模式
option=ChromeOptions()
option.add_argument('--headless')
driver=webdriver.Chrome('chromedriver.exe',options=option)
driver.get('https://www.baidu.com')
driver.get_screenshot_as_png('baidu.png')

四、源码分析

4.1、WebDriver的实例化源码探究

from selenium import webdriver
driver=webdriver.Chrome('./chromedriver.exe')

webdriver.Chrome()实例化WebDrvier的原理如下:

webdriver.py的部分源码

在这个文件中导入了WebDriver类并将WebDriver重命名,追溯WebDriver类可找到如下图的继承关系,WebDriver是继承了RemoteWebDriver类。

继承关系图

4.2、WebDrvierAPI原理探究

下图是webdriverAPI 在 RemoteWebDriver类的调用关系, RemoteWebDriver类的作用是向远程服务器发送命令。

WebDrvierAPI在RemoteWebDrvier类的调用图

该类初始化了服务器连接对象,并发送执行的标准命令,如源码所示标准命令

问题

在selenium的原理执行图中我们可以知道,其实selenium底层是通过http协议来实现的,但是这些字符串是怎么转化成http请求的呢?

解答

RemoteWebDriver类是调用了RemoteConnect类实现发送http请求的

如下图在RemoteConnect类中的调用关系,在_commands字典中定义了每个命令对应的http请求,而且还是restful风格的,关于restful风格可以参考这篇文章Restful风格及实践_杜小白也想的美的博客-CSDN博客

4.3、WebDriverWait的源码探究

from  selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
driver=webdriver.Chrome('./chromedriver.exe')
driver.get('http://www.baidu.com')
wait=WebDriverWait(driver,10)#传入webdriver对象,设置等待时间
#等待节点,返回值是WebElement
result_waitelement=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'.mnav.c-font-normal.c-color-t')))

在上面的一段代码中,实现了对cass属性值为mnav c-font-normal c-color-t元素的等待,如果此节点出现在doc元素树中,将此节点返回封装成WebElement对象返回,那这些是如何实现的呢?

查看WebDriver部分源码

  def __init__(
        self,
        driver,
        timeout: float,
        poll_frequency: float = POLL_FREQUENCY,
        ignored_exceptions: typing.Optional[WaitExcTypes] = None,
    ):

        :Args:
         - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
         - timeout - Number of seconds before timing out
         - poll_frequency - sleep interval between calls
           By default, it is 0.5 second.
         - ignored_exceptions - iterable structure of exception classes ignored during 
        self._driver = driver
        self._timeout = float(timeout)
  def until(self, method, message: str = ""):
        """Calls the method provided with the driver as an argument until the \
        return value does not evaluate to ``False``.

        :param method: callable(WebDriver)
        :param message: optional message for :exc:`TimeoutException`
        :returns: the result of the last call to `method`
        :raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
        """
        screen = None
        stacktrace = None

        end_time = time.monotonic() + self._timeout
        while True:
            try:
                value = method(self._driver)
                if value:
                    return value
            except self._ignored_exceptions as exc:
                screen = getattr(exc, "screen", None)
                stacktrace = getattr(exc, "stacktrace", None)
            time.sleep(self._poll)
            if time.monotonic() > end_time:
                break
        raise TimeoutException(message, screen, stacktrace)

在until方法中的method(self._driver)实际上使用了闭包将

EC.presence_of_element_located((By.CSS_SELECTOR,'.mnav.c-font-normal.c-color-t'))作为内部方法返回,而且还调用了driver对象。

问题

那么什么是EC.presence_of_element_located((By.CSS_SELECTOR,'.mnav.c-font-normal.c-color-t'))呢?调用的这个driver对象是干嘛的呢?

解答

源码如下:

def presence_of_element_located(locator):
    """An expectation for checking that an element is present on the DOM
    of a page. This does not necessarily mean that the element is visible.
    locator - used to find the element
    returns the WebElement once it is located
    """

    def _predicate(driver):
        return driver.find_element(*locator)

    return _predicate

这个方法调用了driver.find_element()

我们在看下其他等待方法,如:text_to_be_present_in_element

源码如下

def text_to_be_present_in_element(locator, text_):
    """An expectation for checking if the given text is present in the
    specified element.
    locator, text
    """

    def _predicate(driver):
        try:
            element_text = driver.find_element(*locator).text
            return text_ in element_text
        except StaleElementReferenceException:
            return False

    return _predicate

它也是调用的drvier.find_element方法,但返回的是bool类型

总结

WaitDriver类实际上调用的是find_element等 WebDriverAPI方法并加入时间等待,一旦在设置的时间内没有找到这个指定元素并抛出超时错误。

​​感谢你阅读到最后~

​​期待你的关注、收藏、评论、点赞~

​​愿我们一起变强

本文仅用于学习交流!!!

标签: selenium python 爬虫

本文转载自: https://blog.csdn.net/weixin_53333436/article/details/129472595
版权归原作者 杜小白也想的美 所有, 如有侵权,请联系我们删除。

“python爬虫——selenium”的评论:

还没有评论