步骤/目录:
1.应用场景
2.准备工作
3.在个人库中添加selenium相关代码
4.selenium个人库使用范例

本文首发于个人博客https://lisper517.top/index.php/archives/46/,转载请注明出处。
本文的目的是讲解selenium,并在个人库中添加相应代码。
本文写作日期为2022年9月10日。

笔者的能力有限,也并非CS从业人员,很多地方难免有纰漏或者不符合代码原则的地方,请在评论中指出。

1.应用场景

在爬虫泛滥的今天,各网站采用了越发先进的方法识别爬虫。如果不是经验丰富的爬虫老手,总会在爬某个网站时翻车(老手也是从翻车一步步过来的)。如果对爬取速度要求不是太高,就可以使用selenium与实际的浏览器搭配爬取网页。
目前很多网站使用了JavaScript在客户端生成页面,selenium的方法就十分适合爬取这些网页;如果无论怎么办都会被识别,笔者能提供的最终解决方案是手动开启浏览器并交给selenium控制。selenium由于要操作浏览器而速度稍慢,这是其唯一的缺点。

2.准备工作

下载selenium:

pip install selenium

另外,edge刚推出时,老版本的selenium还需要下msedge-selenium-tools来帮助扩展edge功能,现在已经不用了。如果新版selenium用不了edge的话,使用 pip install msedge-selenium-tools 来安装这个扩展包,并且selenium也会回到老版本。

下载浏览器:去官网下载chrome、edge、firefox浏览器,注意不要下到别人暗改的版本了。老的爬虫教程可能会推荐下载phantomjs浏览器,这是一个没有界面的浏览器,现在很多网站都对phantomjs浏览器有一些识别与限制,所以已经不建议再使用了,何况chrome、edge、firefox也支持无界面。

最后是下载webdriver,即浏览器驱动,注意的是和浏览器版本匹配。有些时候浏览器升级了,也要及时更换驱动。chrome、edge、firefox的驱动下载网址各自为:

https://chromedriver.chromium.org/downloads
https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
https://github.com/mozilla/geckodriver/releases

以chrome为例,打开浏览器,在网址栏输入 chrome://version/ ,可以看到版本(还有 可执行文件路径、个人资料路径 ,下面可能会用到),根据版本选择对应的chrome驱动即可。

最后,记下 可执行文件路径 ,把三个浏览器的驱动都放在一个文件夹中,比如 D:\webdrivers ,然后在个人库中添加selenium相关的代码。笔者下面将给出在win10机器上selenium的一种使用方法,可根据自身情况对代码进行修改。除了自己对selenium二次开发,还可以看看别人的成品,比如helium。

3.在个人库中添加selenium相关代码

添加一个driver类,需要实现的功能有:根据网页返回网页源码,和返回某个标签对象。

from logging import Logger
import selenium, os
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options as ChromeOptions
# chrome
from selenium.webdriver import Edge
from selenium.webdriver.edge.options import Options as EdgeOptions
#from msedge.selenium_tools import Edge, EdgeOptions
# edge(支持新版edge需要安扩展包pip install msedge-selenium-tools)
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options as FirefoxOptions
# firefox


class driver():
    '''用selenium提供的浏览器类。 driver().help() 可打印帮助。'''
    binary_location_chrome = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
    binary_location_edge = r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
    binary_location_firefox = r'C:\Program Files\Mozilla Firefox\firefox.exe'
    #浏览器可执行文件的路径
    driver_path = r'D:\webdrivers'  #浏览器驱动的位置
    driver_download_url = '''webdriver下载地址:
    https://chromedriver.chromium.org/downloads
    https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
    https://github.com/mozilla/geckodriver/releases
    '''

    userdata_dir_chrome = os.path.join(driver_path, 'userdata_chrome')
    if not os.path.isdir(userdata_dir_chrome):
        os.mkdir(userdata_dir_chrome)
    userdata_dir_edge = os.path.join(driver_path, 'userdata_edge')
    if not os.path.isdir(userdata_dir_edge):
        os.mkdir(userdata_dir_edge)
    userdata_dir_firefox = os.path.join(driver_path, 'userdata_firefox')
    if not os.path.isdir(userdata_dir_firefox):
        os.mkdir(userdata_dir_firefox)

    def __init__(self,
                 kind: str = 'chrome',
                 options=None,
                 headless=True,
                 disguise=True,
                 independent_userdata=True,
                 log: Logger = False) -> None:
        ''':param kind: 指定浏览器的种类,支持 edge/chrome/firefox 
        :param options: 是 EdgeOptions 或 ChromeOptions 或 FirefoxOptions ,与kind对应
        :param headless: 浏览器是否无头(不显示界面)
        :param disguise: 是否伪装成正常浏览器(仅chrome和edge)
        :param independent_userdata: 是否使用独立的userdata。如果开启,该文件夹会在driver_path路径下
        :param log: 可选的log对象,用于输出日志'''
        self.kind = kind
        self.headless = headless
        self.disguise = disguise
        self.independent_userdata = independent_userdata
        self.options = (options, self.get_default_options())[options == None]
        #这里没有检查options和kind是否对应
        self.browser = self.get_browser()
        self.log = log

    def __del__(self):
        self.browser.quit()

    @staticmethod
    def get_options_obj(kind: str,
                        headless: bool = True,
                        disguise: bool = True,
                        independent_userdata: bool = False,
                        userdata_dir: str = ''):
        '''根据浏览器种类,返回一个初始的options对象。
        
        :注意这个方法可静态调用,你可以用这个方法得到初始options、自己加其他参数。
        :param kind: 指定浏览器的种类,支持 edge/chrome/firefox 
        :param headless: 浏览器是否无头(不显示界面)
        :param disguise: 是否伪装成正常浏览器(仅chrome和edge)
        :param independent_userdata: 是否使用独立的userdata。如果开启,需要指定userdata的路径
        :param userdata_dir: userdata的存放路径'''
        options = None
        try:
            if kind == 'chrome':
                options = ChromeOptions()
                options.binary_location = driver.binary_location_chrome
                options.add_argument('--disable-gpu')  #据说谷歌文档提到需要加上这个属性来规避bug
            elif kind == 'edge':
                options = EdgeOptions()
                options.use_chromium = True
                options.binary_location = driver.binary_location_edge
            elif kind == 'firefox':
                options = FirefoxOptions()
                options.binary_location = driver.binary_location_firefox
            else:
                raise Exception(
                    'error: only support chrome/firefox/edge, please check')
            if headless:
                options.add_argument('--headless')
            if disguise:
                options.add_experimental_option('excludeSwitches',
                                                ['enable-automation'])
                #firefox的options没有add_experimental_option
            if independent_userdata:
                if userdata_dir == '' or not os.path.isdir(userdata_dir):
                    print(
                        'userdata_dir wrong, please input correct userdata_dir'
                    )
                else:
                    userdata_dir = userdata_dir.replace(
                        '\\', '\\\\')  #此处仅为windows系统设置路径
                    options.add_argument(
                        '--user-data-dir={}'.format(userdata_dir))
            return options
        except:
            raise Exception('driver.get_option_obj failed')

    def get_default_options(self):
        '''生成浏览器的默认配置。'''
        userdata_dir = ''
        if self.kind == 'chrome':
            userdata_dir = driver.userdata_dir_chrome
        elif self.kind == 'edge':
            userdata_dir = driver.userdata_dir_edge
        elif self.kind == 'firefox':
            userdata_dir = driver.userdata_dir_firefox
        else:
            raise Exception(
                'error: only support chrome/firefox/edge, please check')
        return driver.get_options_obj(self.kind, self.headless, self.disguise,
                                      self.independent_userdata, userdata_dir)

    def get_browser(self):
        '''根据配置返回浏览器。'''
        kind, driver_path = self.kind, driver.driver_path
        driver_browser, options = None, self.options
        try:
            if kind == 'chrome':
                driver_browser = Chrome(executable_path=os.path.join(
                    driver_path, 'chromedriver.exe'),
                                        options=options)
            elif kind == 'edge':
                driver_browser = Edge(executable_path=os.path.join(
                    driver_path, 'msedgedriver.exe'),
                                      options=options)
            elif kind == 'firefox':
                driver_browser = Firefox(executable_path=os.path.join(
                    driver_path, 'geckodriver.exe'),
                                         options=options)
            return driver_browser
        except selenium.common.exceptions.TimeoutException:
            raise Exception('driver.get_browser failed')

    def get_page_source(self,
                        url: str,
                        XPath_expr: str = '',
                        timeout: int = 60) -> str:
        '''通过浏览器对象获取目标网页源码。

        :param url: 目标网页
        :param XPath_expr: XPath表达式,用于让页面显式等待
        :param timeout: 等待时间
        :return 网页源码'''
        browser, log = self.browser, self.log
        """script = '''Object.defineProperty(navigator, 'webdriver', {get: ()= > undefined})'''
        browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument",
                                {"source": script})
        #上面这段是运行js代码,将navigator的属性webdriver改为undefined,可隐藏爬虫身份
        #如果设置了 disguise=True ,就不需要这个了"""

        try:
            browser.get(url)
            if XPath_expr != '':
                WebDriverWait(browser, timeout).until(
                    EC.presence_of_element_located((By.XPATH, XPath_expr)))
        except selenium.common.exceptions.TimeoutException or selenium.common.exceptions.NoSuchElementException:
            browser.implicitly_wait(10)
            message = '浏览器等待超时,需要的元素仍未出现。隐式等待10s并返回页面源码,请检查是否正确'
            if log:
                log.info(message)
            else:
                print(message)
        return browser.page_source

    def get_tag_obj(self, url: str, XPath_expr: str, timeout: int = 60):
        '''通过浏览器对象获取目标标签。

        :param url: 目标网页
        :param XPath_expr: XPath表达式,一般来说就是目标标签,页面将为该标签显式等待
        :param timeout: 等待时间
        :return 标签对象'''
        browser, log = self.browser, self.log
        try:
            browser.get(url)
            element = WebDriverWait(browser, timeout).until(
                EC.presence_of_element_located((By.XPATH, XPath_expr)))
            return element
        except selenium.common.exceptions.TimeoutException or selenium.common.exceptions.NoSuchElementException:
            message = '浏览器等待超时,需要的元素仍未出现;或许XPath表达式书写错误,请检查'
            if log:
                log.info(message)
            else:
                print(message)
            return

    @staticmethod
    def help():
        '''打印帮助信息。'''
        help_info = r"""注意,以下内容很多尚未验证。
目录:一、如何用 options 对象及其他选项设置浏览器的行为
二、browser对象的一些操作
三、对标签对象的鼠标操作
四、对标签对象的其他操作
五、显式等待与隐式等待
六、人工打开浏览器并交给程序控制


一、如何用 options 对象及其他选项设置浏览器的行为
1.options基本的设置方法(参数中用字符串指定设置):
(1)添加参数:options.add_argument()
(2)添加扩展应用:options.add_extension() 和 options.add_encoded_extension()
(3)添加实验性质的设置参数:options.add_experimental_option() 。firefox没有这个选项,所以仅用于chrome和edge

2.options一些常用的设置
(1)设置以开发者模式启动,并且webdriver相关的一些属性改回正常值。这个模式可以骗过爬虫识别
options.add_experimental_option('excludeSwitches', ['enable-automation'])
(2)允许浏览器弹窗。如果需要用浏览器打开不只一个页面,必须把这个设置加上
options.add_argument("--disable-popup-blocking")
(3)浏览器使用代理
options.add_argument("--proxy-server=http或https://代理ip:端口")
(4)不加载图片。如果不爬取图片,这个选项可提升速度
options.add_argument('blink-settings=imagesEnabled=false')
(5)禁用GPU显卡辅助渲染。谷歌文档提到需要加上这个属性来规避bug
chrome_options.add_argument('--disable-gpu')
(6)无头浏览器,即浏览器不提供可视化页面。无桌面的linux必须加这条
options.add_argument('--headless')
(7)隐藏滚动条。可用于一些特殊页面
options.add_argument('--hide-scrollbars')
(8)改变UA
options.add_argument('user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"')
(9)指定浏览器分辨率
options.add_argument('window-size=1920x3000')
(10)在沙箱之外运行(以最高权限运行)
options.add_argument('--no-sandbox')
(11)手动指定使用的浏览器位置
options.binary_location = r"浏览器可执行文件的路径"
(12)添加插件(crx文件是chrome的插件文件)
options.add_extension('crx文件路径')
(13)禁用JavaScript
options.add_argument("--disable-javascript")


二、browser对象的一些操作
在其他地方,browser对象也常被称为driver对象。
1.browser对象的元素定位
可以使用id、name、tag_name等定位。有如下方法可使用:
browser.find_elements_by_id()
browser.find_elements_by_name()
browser.find_elements_by_class_name()
browser.find_elements_by_tag_name()
browser.find_elements_by_link_text()
browser.find_elements_by_partial_link_text()
browser.find_elements_by_xpath()
browser.find_elements_by_css_selector()
其中最推荐的是最后两个,通过xpath和css_selector查找元素。
上述方法返回的都是匹配的标签序列;把 elements 改成 element 则只返回第一个匹配的标签。

另外,4.0之后的selenium据说不支持 find_elements_by_id 这些方法了,需要改成:
from selenium.webdriver.common.by import By
browser.find_element(By.ID, "ID名")
browser.find_element(By.NAME, "标签名")
下文也有关于By的应用。

2.browser对象的其他方法和属性
(1)到前一页或后一页:browser.forward() 和 browser.back()
(2)刷新:browser.refresh()
(3)获取信息:browser有一些属性,比如 browser.page_source、browser.title、browser.current_url 分别为当前页面的源码、标题、网址
(4)切换窗口或框架: switch_to_window("windowName") 和 switch_to_frame("frameName") 。框架,比如弹出一个输入用户密码的小框框就叫框架。
(5)返回到父frame:switch_to_default_content()
(6)切换到警告框:a = browser.switch_to_alert()
警告框可以像标签对象一样操作。方法和属性有:a.text、a.accept()、a.dismiss()、a.send_keys() ,分别是警告内容、接受警告、关闭警告、发送按键。send_keys方法详见后。
(7)操作cookie:
获得所有cookie信息(字典形式):get_cookies()
返回cookie中键对应的值:get_cookie("键名")
添加键值对到cookie(对已存在的键可能会替换其值):add_cookie(cookie_dict)
删除cookie信息:delete_cookie("键名", optionsString) 。其中 optionsString 是该cookie的选项,目前支持的选项包括 路径、域
删除所有cookie:delete_all_cookies()
(8)执行javascript:execute_script(js_code) ,js_code是字符串形式的js代码
(9)屏幕截图:get_screenshot_as_file(r"图片保存路径") ,这个方法可用于无头时查找问题
(10)关闭与退出:close() 关闭单个窗口,quit() 关闭所有窗口并退出
(11)页面最多加载几秒:browser.set_page_load_timeout(timeout)
页面的js最多加载几秒:browser.set_script_timeout(timeout)


三、对标签对象的鼠标操作
鼠标操作称为动作链,在设置完所有动作后用perform()方法执行。
1.一般格式:
from selenium.webdriver.common.action_chains import ActionChains
#a为一个标签对象
a = browser.find_elements_by_xpath(r'XPath表达式')
AC = ActionChains(browser).move_to_element(a)
#其他动作
AC.perform() #设置完所有动作后用perform()方法执行

2.常用的鼠标操作
左键单击:click()
右键单击:context_click()
左键双击:double_click()
拖动并松开:drag_and_drop()
在元素处悬停:move_to_element()


四、对标签对象的其他操作
常见的有输入文本,提交,键盘按键。
(1)对一个标签输入文本。常用于填写用户名密码
browser.find_element_by_xpath(r'XPath表达式').send_keys("待输入的文本")
(2)清除标签中的文本。输入框里如果有默认值,可以用这个方法清除
标签对象.clear()
(3)提交表单:submit()
(4)元素的尺寸:size
(5)元素的内容:text
(6)获得属性值:get_attribute("属性名")
(7)元素是否可见:is_displayed()

键盘输入按键:
BackSpace:标签对象.send_keys(Keys.BACK_SPACE)
Space:send_keys(Keys.SPACE)
Tab:send_keys(Keys.TAB)
Esc:send_keys(Keys.ESCAPE)
Enter:send_keys(Keys.ENTER)
Ctrl+A:send_keys(Keys.CONTROL,'a')
Ctrl+C:send_keys(Keys.CONTROL,'c')
Ctrl+X:send_keys(Keys.CONTROL,'x')
Ctrl+V:send_keys(Keys.CONTROL,'v')
F1:send_keys(Keys.F1)

下拉框选择值:
定位到下拉框标签,然后用:Select(下拉框标签对象).select_by_value('50') 类似的方法选择值


五、显式等待与隐式等待
selenium一般是用来爬取一些有js的网页,而js加载需要一定时间
可以用显式等待和隐式等待来让浏览器对象度过这段时间,前者更优
隐式等待:等待固定的秒数,使用 browser.implicitly_wait(秒数) 即可
显式等待:等待某个元素被js加载出来,一般格式如下:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(browser, 5, 0.5).until(EC.presence_of_element_located((By.XPATH, "XPath表达式")))

WebDriverWait(浏览器对象,最多等待几秒,每过多少秒检查一次) ,超时后会抛出NoSuchElementException异常
WebDriverWait类除了until方法,还有until_not方法。
EC.presence_of_element_located() 方法需要传入 locator 作为参数,这里用的是 (By.XPATH, "XPath表达式")


六、人工打开浏览器并交给程序控制
有时想尽办法也会被网站识别出爬虫,这时可以试试人工打开浏览器并交给程序控制。以win10系统为例,流程如下:
1.在cmd或终端中输入命令、打开浏览器,比如:
chrome.exe --remote-debugging-port=9222 --user-data-dir="D:\webdrivers\userdata_chrome"
msedge.exe --remote-debugging-port=9222 --user-data-dir="D:\webdrivers\userdata_edge"
firefox.exe --remote-debugging-port=9222 --user-data-dir="D:\webdrivers\userdata_firefox"
--user-data-dir 参数用于设置临时的userdata文件夹,这个参数必须也加上。
如果无法找到.exe文件,可以在cmd中进入浏览器的根目录,或把根目录加到系统路径中。

2.在options对象中添加: options.add_experimental_option('debuggerAddress', '127.0.0.1:9222') 即可。
"""
        print(help_info)

selenium的使用过程大概就是指定浏览器,指定浏览器设置(options),打开浏览器即可。详细的使用注意事项都在 driver().help() 里了,下面来试试自建库的功能。

4.selenium个人库使用范例

打开一个chrome浏览器,打开bing搜索:

from MyPythonLib import spider #这一行是笔者的自建库

driver = spider.driver('chrome', headless=False)
browser = driver.browser

browser.get('http://bing.com')
input()

手动打开浏览器,交给selenium控制:
以windows、chrome为例,在cmd中输入:

cd C:\Program Files\Google\Chrome\Application
chrome.exe --remote-debugging-port=9222 --user-data-dir="D:\webdrivers\userdata_chrome"

注意 D:\webdrivers\userdata_chrome 需要提前创建好。最后在py文件中写入:

from MyPythonLib import spider

options = spider.driver.get_options_obj('chrome',
                                        headless=False,
                                        disguise=False)
options.add_experimental_option('debuggerAddress', '127.0.0.1:9222')
driver = spider.driver('chrome', options=options)
browser = driver.browser
browser.get('http://bing.com')

就能看到自己刚才打开的浏览器从初始页面变成bing搜索。

如果需要自己定制options,也按上面的方法,在构造driver时传入需要的options对象即可。笔者为了方便,添加了 spider.driver.get_options_obj() 方法,返回一个默认的options。

标签: python, selenium

添加新评论