爬虫教程( 5 ) --- Selenium、PhantomJS、selenium反检测、Playwright、Playwright-python、scrapy-playwright

编程入门 行业动态 更新时间:2024-10-18 06:06:36

参考:http://cuiqingcai/2599.html
Selenium教程:https://www.yiibai/selenium
selenium 官方参考文档:https://selenium-python.readthedocs.io/index.html
Selenium Documentation:https://www.seleniumhq/docs
selenium模拟登陆, 搜索关键词:https://blog.csdn/u010454729/article/details/51225388

浏览器插件:Selenium IDE

1、Selenium

中式读法:【 瑟林捏幕 】

Selenium( selenium 中文网:http://www.selenium/ )是一个强大的网络数据采集工具,最初是为了网站自动化测试而开发的,被用来测试 Web 应用程序在不同的浏览器和操作系统上运行能力。

Selenium 不带浏览器,它需要与第三方浏览器结合在一起使用。例如,如果你在 Chrome 上运行 Selenium

Selenium 是什么

        Selenium 是什么?一句话,自动化测试工具。简单的说就是一个可以用代码操所浏览器的工具。可以通过selenium进行搜索关键字,点击按钮等等操作。它支持各种浏览器,包括 Chrome,Safari,Firefox 等主流界面式浏览器,如果你在这些浏览器里面安装一个 Selenium 的插件,那么便可以方便地实现Web界面的测试。换句话说 Selenium 可以自动化操作这些浏览器,但是必须下载并设置不同的浏览器驱动(注:部分浏览器驱动地址需要科学上网)。

selenium 是一套完整的web应用程序测试系统,包含了测试的录制(selenium IDE),编写及运行(Selenium Remote Control)和测试的并行处理(Selenium Grid)。Selenium的核心Selenium Core基于JsUnit,完全由JavaScript编写,因此用于任何支持JavaScript的浏览器上。

selenium 可以模拟真实浏览器,自动化测试工具,支持多种浏览器,爬虫中主要用来解决JavaScript渲染问题。

Selenium 支持多种语言开发,比如 Java,C,Ruby、Python 等。

        嗯,所以呢?安装一下 Python 的 Selenium 库,再安装好 PhantomJS,不就可以实现 Python+Selenium+PhantomJS 的无缝对接了嘛!PhantomJS 用来渲染解析JS,Selenium 用来驱动以及与 Python 的对接,Python 进行后期的处理,完美的三剑客!

有人问,为什么不直接用浏览器而用一个没界面的 PhantomJS 呢?答案是:效率高!

用python写爬虫的时候,主要用的是selenium的Webdriver,我们可以通过下面的方式先看看Selenium.Webdriver支持哪些浏览器。首先导入 webdriver 模块。然后使用help函数

from selenium import webdriver
help(webdriver)

安装、使用

首先安装 Selenium:pip install selenium

安装 浏览器 驱动 webdriver
        1. chromedriver 下载地址:http://chromedriver.chromium
            chromedriver 镜像地址 :CNPM Binaries Mirror
        2. Firefox 的驱动 geckodriver 下载地址:Releases · mozilla/geckodriver · GitHub
        3. IE 驱动 下载地址:NuGet Gallery | Selenium.WebDriver.IEDriver 4.8.1

注意:下载解压后,将chromedriver.exe , geckodriver.exe , Iedriver.exe 放到 Python 的安装目录,例如 D:\python 。 然后再将 Python 的安装目录添加到系统环境变量的 Path

爬虫 Selenium Chromium 与 Chromedriver对应版本( 注意是 chromium,不是 Chrome ):
淘宝镜像地址在每个文件夹的 notes.txt 中存有 chromium 和 Chromedriver 的版本对应。

from selenium import webdriver

browser = webdriver.Chrome()
# browser = webdriver.Firefox()
# browser = webdriver.Ie()

browser.get('https://www.baidu/')
__input = input("暂停, 按任意键继续")

运行这段代码,会自动打开浏览器,然后访问百度。

如果程序执行错误,浏览器没有打开,那么应该是没有装 Chrome 浏览器或者 Chrome 驱动没有配置在环境变量里。下载驱动,然后将驱动文件路径配置在环境变量即可。

模拟提交

下面的代码实现了“模拟百度提交搜索”的功能。首先等页面加载完成,然后输入关键字到搜索框文本,最后点击提交。

from selenium import webdriver
from selenium.webdrivermon.keys import Keys
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
browser.get("https://www.baidu")
print(browser.title)
elem = browser.find_element(By.NAME, value="wd")  # 百度首页搜索框 name="wd"
elem.send_keys("MM")         # 输入关键字
elem.send_keys(Keys.RETURN)  # 模拟 点击 enter键,提交
print(browser.page_source)   # 打印 js 渲染后的网页
__input = input("暂停, 按enter键退出")

其中 driver.get 方法会打开请求的 URL,WebDriver 会等待页面完全加载完成之后才会返回,即程序会等待页面的所有内容加载完成,JS渲染完毕之后才继续往下执行。注意:如果这里用到了特别多的 Ajax 的话,程序可能不知道是否已经完全加载完毕。

WebDriver 提供了许多寻找网页元素的方法,通过 By 类的方法。

然后,输入文本,模拟点击回车。利用 Keys 这个类来模拟键盘输入。就像敲击键盘一样。

注意:获取网页渲染后的源代码。输出 page_source 属性即可。

通过 dir(browser) 查看,browser 对象有那些方法, 

测试用例

import unittest
from selenium import webdriver
from selenium.webdrivermon.keys import Keys
from selenium.webdrivermon.by import By


class PythonOrgSearch(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome()

    def test_search_in_python_org(self):
        driver = self.driver
        driver.get("https://www.python")
        self.assertIn("Python", driver.title)
        elem = driver.find_element(By.NAME, value="q")
        elem.send_keys("pycon")
        elem.send_keys(Keys.RETURN)
        assert "No results found." not in driver.page_source

    def tearDown(self):
        self.driver.close()


if __name__ == "__main__":
    unittest.main()

测试用例是继承了 unittest.TestCase 类,继承这个类表明这是一个测试类。setUp方法是初始化的方法,这个方法会在每个测试类中自动调用。每一个测试方法命名都有规范,必须以 test 开头,会自动执行。最后的 tearDown 方法会在每一个测试方法结束之后调用。这相当于最后的析构方法。在这个方法里写的是 close 方法,你还可以写 quit 方法。不过 close 方法相当于关闭了这个 TAB 选项卡,然而 quit 是退出了整个浏览器。当你只开启了一个 TAB 选项卡的时候,关闭的时候也会将整个浏览器关闭。

import time
from selenium import webdriver
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
browser.maximize_window()  # 最大化浏览器
browser.implicitly_wait(20)  # 设置隐式时间等待
url = 'https://www.baidu'
browser.get(url)

# 网页的登录按钮
btn_login = browser.find_element(By.ID, value='s-top-loginbtn')
btn_login.click()  # 点击登录按钮
time.sleep(2)  # 显式等待 2s

# 查找所有的 a 标签
all_link = browser.find_element(By.TAG_NAME, value='a')
link = all_link[3]  # 提取 第四个 a 标签
link.click()  # 点击 提取 的 a 标签
input('暂停,按 enter 退出...')

页面操作

访问页面  

运行后自动打开Chrome浏览器,并登陆百度打印百度首页的源代码,然后关闭浏览器

import time
from selenium import webdriver
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
browser.get("https://www.baidu")
print(browser.page_source)
browser.close()

执行JavaScript

这是一个非常有用的方法,这里就可以直接调用js方法来实现一些操作,下面的例子是通过登录知乎然后通过js翻到页面底部,并弹框提示

from selenium import webdriver

browser = webdriver.Chrome()
browser.get("https://www.zhihu/explore")
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')
input('暂停,按 enter 退出')
browser.close()

查找 ( 定位 ) 元素

Selenium常见元素定位方法和操作的学习介绍:[python爬虫] Selenium常见元素定位方法和操作的学习介绍_selenium元素定位_Eastmount的博客-CSDN博客

Selenium切换窗口句柄及调用Chrome浏览器:[python爬虫] Selenium切换窗口句柄及调用Chrome浏览器_selenium 窗口句柄_Eastmount的博客-CSDN博客

查找单个元素

import time
from selenium import webdriver
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
browser.get("https://www.taobao")
input_first = browser.find_element(By.ID, "q")
print(input_first)
input_second = browser.find_element(By.CSS_SELECTOR, "#q")
print(input_second)
input_third = browser.find_element(By.XPATH, '//*[@id="q"]')
print(input_third)
browser.close()

这里通过三种不同的方式获取元素。结果都是相同的。

查找元素的方法

Selenium 提供了8种定位方式:id、name、class name、tag name、link text、partial link text、xpath、css selector。

By 类的一些属性如下

ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"

通过 By 模块 定位元素

from selenium import webdriver
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()

browser.get("https://www.taobao")
input_first = browser.find_element(By.ID, "q")
print(input_first)
browser.close()

多个元素查找

  • 单个元素是 find_element,
  • 多个元素是 find_elements,结果是获得一个元素列表

示例:lis = browser.find_elements(By.CSS_SELECTOR,'.service-bd li')

页面交互 ( 点击、输入 )

玩转python selenium鼠标键盘操作(ActionChains):玩转python selenium鼠标键盘操作(ActionChains)_python_脚本之家
Selenium鼠标与键盘事件常用操作方法示例:Selenium鼠标与键盘事件常用操作方法示例_python_脚本之家

找到页面元素后,就需要和页面交互,比如:点击,输入等等。

需要导入包:from selenium.webdrivermon.keys import Keys

ele.send_keys("some text") # 文本框输入
ele.send_keys("and some", Keys.ARROW_DOWN)
ele.clear() # 清除 文本框 内容

Selenium 所有的 api 文档:7. WebDriver API — Selenium Python Bindings 2 documentation

交互动作 --- 动作链 (拖动、移动等)

将动作附加到动作链中串行执行

from selenium import webdriver
from selenium.webdrivermon.by import By
from selenium.webdrivermon.keys import Keys
from selenium.webdriver import ActionChains


browser = webdriver.Chrome()

url = "https://www.runoob/try/try.php?filename=jqueryui-api-droppable"
browser.get(url)
browser.switch_to.frame('iframeResult')

source = browser.find_element(By.CSS_SELECTOR, '#draggable')
target = browser.find_element(By.CSS_SELECTOR, '#droppable')

actions = ActionChains(browser)
actions.drag_and_drop(source, target)
actions.perform()

actions.drag_and_drop_by_offset(source, 400, 0).perform()
input('暂停,按 enter 退出')
browser.close()

更多操作参考:7. WebDriver API — Selenium Python Bindings 2 documentation

元素拖拽

要完成元素的拖拽,首先你需要指定被拖动的元素和拖动目标元素,然后利用 ActionChains 类来实现

element = driver.find_element_by_name("source")
target = driver.find_element_by_name("target")

from selenium.webdriver import ActionChains
action_chains = ActionChains(driver)
action_chains.drag_and_drop(element, target).perform()
actions.drag_and_drop_by_offset(element, 400, 0).perform()

这样就实现了元素从 source 拖动到 target 的操作

chrome 浏览器打开标签页的快捷键是 ctrl+t,那把 ctrl+t 的按键事件传入即可

from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdrivermon.by import By
from selenium.webdrivermon.keys import Keys
from selenium.webdriver import ActionChains

browser = webdriver.Chrome()
browser.maximize_window()
url = 'https://www.baidu'
browser.get(url)

ac = ActionChains(browser)
ac.key_down(Keys.CONTROL).key_down('t').key_up(Keys.CONTROL).key_up('t').perform()
input("暂停,按 enter 退出...")
browser.close()

import time
from selenium import webdriver
from selenium.webdrivermon.keys import Keys
from selenium.webdrivermon.by import By

browser = webdriver.Chrome() # 默认的火狐浏览器
for i in range(5):
# 这句代码相当于在浏览器窗口下按下ctrl+t打开一个新的标签页
browser.find_element(By.TAG_NAME, 'body').send_keys(Keys.CONTROL + 't')
time.sleep(10) # 等待所有窗口完全打开,10秒够用了, 如果不打开得不到所有句柄,只能得到部分。
handles = browser.window_handles
print(len(handles))
print(handles)
input('暂停,按 enter 退出')
browser.close()

通常要确保页面加载完成,可以使用 selenium.webdriver.support.ui.WebDriverWait 

打开新窗口示例代码:

from selenium import webdriver
# 打开谷歌浏览器
browser = webdriver.Chrome()
 
# 打开窗口
browser.get("https://www.baidu/")
# 打开新窗口
newwindow_js = 'window.open("https://www.baidu");'  # js 
browser.execute_script(newwindow_js)                    # 执行 js 打开新的页面 
 
# 切换到新的窗口
handles = browser.window_handles        # 得到 所有页面  句柄
browser.switch_to_window(handles[-1])   # 切换焦点到 对应页面

获取元素属性

get_attribute('class')

from selenium import webdriver
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
url = 'https://www.zhihu/explore'
browser.get(url)
logo = browser.find_element(By.ID, 'zh-top-link-logo')
print(logo)
print(logo.get_attribute('class'))
browser.close()

获取 文本值、ID、位置、标签名

id、location、tag_name、size

from selenium import webdriver
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
url = 'https://www.zhihu/explore'
browser.get(url)
tag_input = browser.find_element(By.CLASS_NAME, 'zu-top-add-question')
print(tag_input.text)
print(tag_input.id)
print(tag_input.location)
print(tag_input.tag_name)
print(tag_input.size)

下拉 选项卡

下拉选项卡的的处理

tag_select = browser.find_element(By.XPATH, "//select[@name='name']")
all_options = tag_select.find_elements(By.TAG_NAME, "option")
for option in all_options:
    print(f'Value is: {option.get_attribute("value")}')
    option.click()

首先获取了第一个 select 元素,也就是下拉选项卡。然后轮流设置了 select 选项卡中的每一个 option 选项。你可以看到,这并不是一个非常有效的方法

其实 WebDriver 中提供了一个叫 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 = Select(driver.find_element_by_id('id'))
select.deselect_all()
这样便可以取消所有的选择。
另外我们还可以通过下面的方法获取所有的已选选项。
select = Select(driver.find_element_by_xpath("xpath"))
all_selected_options = select.all_selected_options
获取所有可选选项是
options = select.options
如果你把表单都填好了,最后肯定要提交表单对吧。怎吗提交呢?很简单
driver.find_element_by_id("submit").click()
这样就相当于模拟点击了 submit 按钮,做到表单提交。
当然你也可以单独提交某个元素
element.submit()
方法,WebDriver 会在表单中寻找它所在的表单,如果发现这个元素并没有被表单所包围,那么程序会抛出 NoSuchElementException 的异常。

Frame

在很多网页中都是有Frame标签,所以我们爬取数据的时候就涉及到切入到frame中以及切出来的问题,通过下面的例子演示
这里常用的是 switch_to.from() 和 switch_to.parent_frame()

import time
from selenium import webdriver
from seleniummon.exceptions import NoSuchElementException

browser = webdriver.Chrome()
url = 'http://www.runoob/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
source = browser.find_element_by_css_selector('#draggable')
print(source)
try:
    logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
    print('NO LOGO')
browser.switch_to.parent_frame()
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)

页面切换 和 打开新窗口

Python+Selenium练习篇之27-多窗口之间切换 :Python+Selenium练习篇之27-多窗口之间切换_Anthony_tester的博客-CSDN博客
python selenium打开新窗口,多窗口切换:python selenium打开新窗口,多窗口切换_蘑菇猎手的博客-CSDN博客
python/selenium/chrome打开新窗口并实现窗口切换: python + selenium + chrome 如何打开新窗口,并实现窗口切换_谷歌浏览器新窗口代码_Kosmoo的博客-CSDN博客

selenium之 定位以及切换frame(iframe):selenium之 定位以及切换frame(iframe)_selenium切换到iframe_huilan_same的博客-CSDN博客

关键字:selenium iframe 切换

如果在一个页面上点击一个链接之后,并不是在当前页面上打开,而是重新打开一个新页面;这种情况下如何跳转到新的页面上操作?首先,需要了解的是每个窗口都有句柄的,可以理解为浏览器窗口的唯一标识符,根据这个标识符来确定新打开的窗口。打开新页面后,selenium 的 focus 还是在 原来的页面上,所以需要使用 switch_to.window 方法把 焦点(focus) 切换到新页面上

如果是新打开的 iframe 就使用 switch_to_frame('xxx')
如果是新打开的 tab 就使用 switch_to_window('')

一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下

driver.switch_to_window("windowName")

switch_to_window 方法现在已经废弃,鼠标放在这个方法上提示 使用 switch_to.window 代替

另外你可以使用 window_handles 方法来获取每个窗口的操作对象。例如

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

另外切换 frame 的方法如下

driver.switch_to_frame("frameName.0.child")

这样焦点会切换到一个 name 为 child 的 frame 上。

打开新窗口主要使用 JavaScript 实现:

# 新标签页打开这个url
js="window.open("url")"
driver.execute_script(js)
time.sleep(2)

选项卡 管理

通过执行 js 命令实现新开选项卡 window.open()
不同的选项卡是存在列表里 browser.window_handles
通过 browser.window_handles[0] 就可以操作第一个选项卡

import time
from selenium import webdriver
from selenium.webdrivermon.keys import Keys
from selenium.webdrivermon.by import By

browser = webdriver.Chrome()
browser.get('https://www.baidu')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://python')
input('暂停, 按enter退出...')
browser.close()

弹窗处理

页面出现了弹窗

alert = driver.switch_to_alert()

通过上述方法可以获取弹窗对象。

  

历史记录 ( 前进、后退 )

前进 和 后退 针对的是 浏览器浏览的网页 的 历史记录

driver.forward()
driver.back()

Cookies 处理

get_cookies()
delete_all_cookes()
add_cookie()

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.zhihu/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu', 'value': 'zhaofan'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())

为页面添加 Cookies,用法如下

# Go to the correct domain
driver.get("http://www.example")

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

获取页面 Cookies,用法如下

# Go to the correct domain
driver.get("http://www.example")

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

以上便是 Cookies 的处理,同样是非常简单的。

页面 ( 隐式、显示) 等待

Python selenium 三种等待方式详解(必会):Python selenium 三种等待方式详解(必会)_python_脚本之家

        这是非常重要的一部分,现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。这会让元素定位困难而且会提高产生 ElementNotVisibleException 的概率。所以 Selenium 提供了两种等待方式,

  • 隐式等待:等待特定的时间。如果 WebDriver没有在 DOM中找到元素,将继续等待,超出设定时间后则抛出找不到元素的异常。默认的时间是0
  • 显式等待:首先指定一个等待条件,并且再指定一个最长等待时间,然后在这个时间段内进行判断是否满足等待条件,如果成立就会立即返回,如果不成立,就会一直等待,直到等待你指定的最长等待时间,如果还是不满足,就会抛出异常,如果满足了就会正常返回
from selenium import webdriver
from selenium.webdrivermon.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
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()

程序默认会 500ms 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。

from selenium import webdriver
from selenium.webdrivermon.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


browser = webdriver.Chrome()
browser.get('https://www.taobao/')

wait = WebDriverWait(browser, 10)
tag_input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)

上述的例子中的条件:EC.presence_of_element_located()是确认元素是否已经出现了
EC.element_to_be_clickable()是确认元素是否是可点击的

下面是一些内置的等待条件,你可以直接调用这些条件,而不用自己写某些等待条件了。

常用的判断条件:
title_is                    标题是某内容
title_contains          标题包含某内容
presence_of_element_located      元素加载出,传入定位元组,如(By.ID, 'p')
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 frame      加载并切换
invisibility_of_element_located                              元素不可见
element_to_be_clickable                                       元素可点击
staleness_of                        判断一个元素是否仍在DOM,可判断页面是否已经刷新
element_to_be_selected       元素可选择,传元素对象
element_located_to_be_selected 元素可选择,传入定位元组
element_selection_state_to_be 传入元素对象以及状态,相等返回True,否则返回False
element_located_selection_state_to_be 传入定位元组以及状态,相等返回True,否则返回False
alert_is_present 是否出现Alert

更多操作参考:7. WebDriver API — Selenium Python Bindings 2 documentation

隐式等待

隐式等待比较简单,就是简单地设置一个等待时间,单位为秒。到了一定的时间发现元素还没有加载,则继续等待我们指定的时间,如果超过了我们指定的时间还没有加载就会抛出异常,如果没有需要等待的时候就已经加载完毕就会立即执行

from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

当然如果不设置,默认等待时间为0。

from selenium import webdriver

browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu/explore')
input = browser.find_element_by_class_name('zu-top-add-question')
print(input)

异常处理

这里的异常比较复杂,官网的参考地址:
7. WebDriver API — Selenium Python Bindings 2 documentation
这里只进行简单的演示,查找一个不存在的元素

from selenium import webdriver
from seleniummon.exceptions import TimeoutException, NoSuchElementException

browser = webdriver.Chrome()
try:
    browser.get('https://www.baidu')
except TimeoutException:
    print('Time Out')
try:
    browser.find_element_by_id('hello')
except NoSuchElementException:
    print('No Element')
finally:
    browser.close()

2、PhantomJS

************************* PhantomJS 已经停止更新 *************************

中式读法:【 饭特姆JS 】

PhantomJS(官网: http://phantomjs/ )是一个基于 WebKit 内核、无 UI 界面的浏览器,WebKit 是一个开源的浏览器引擎。比如,主流的 Safari、Google Chrome、傲游3、猎豹浏览器、百度浏览器、opera浏览器 都是基于 Webkit 开发。)

PhantomJS 会把网站数据加载到内存中,并执行页面上的 JavaScript,但不会向用户展示图形界面。

PhantomJS 是一个无界面的,可脚本编程的WebKit浏览器引擎。它原生支持多种web 标准:DOM 操作,CSS选择器,JSON,Canvas 以及SVG。

官方网站:http://phantomjs/download.html

Examples:http://phantomjs/examples/index.html

安装完成之后命令行输入:phantomjs -v

如果正常显示版本号,那么证明安装成功了。如果提示错误,那么请重新安装。

快速开始

第一个程序

第一个程序当然是Hello World,新建一个 js 文件。命名为 helloworld.js

console.log('Hello, world!');
phantom.exit();

命令行输入

phantomjs helloworld.js

程序输出了 Hello,world!程序第二句话终止了 phantom 的执行。

注意:phantom.exit();这句话非常重要,否则程序将永远不会终止。

页面加载

可以利用 phantom 来实现页面的加载,下面的例子实现了页面的加载并将页面保存为一张图片。

var page = require('webpage').create();
page.open('http://example', function (status) {
    console.log("Status: " + status);
    if (status === "success") {
        page.render('example.png');
    }
    phantom.exit();
});

首先创建了一个webpage对象,然后加载本站点主页,判断响应状态,如果成功,那么保存截图为 example.png

以上代码命名为 pageload.js,命令行

phantomjs pageload.js

发现执行成功,然后目录下多了一张图片,example.png

因为这个 render 方法,phantom 经常会用到网页截图的功能。

测试页面加载速度

下面这个例子计算了一个页面的加载速度,同时还用到了命令行传参的特性。新建文件保存为 loadspeed.js

var page = require('webpage').create(),
  system = require('system'),
  t, address;

if (system.args.length === 1) {
  console.log('Usage: loadspeed.js <some URL>');
  phantom.exit();
}

t = Date.now();
address = system.args[1];
page.open(address, function(status) {
  if (status !== 'success') {
    console.log('FAIL to load the address');
  } else {
    t = Date.now() - t;
    console.log('Loading ' + system.args[1]);
    console.log('Loading time ' + t + ' msec');
  }
  phantom.exit();
});

程序判断了参数的多少,如果参数不够,那么终止运行。然后记录了打开页面的时间,请求页面之后,再纪录当前时间,二者之差就是页面加载速度。

phantomjs loadspeed.js http://example

运行结果

Loading http://example
Loading time 11678 msec

这个时间包括JS渲染的时间,当然和网速也有关。

代码评估

利用 evaluate 方法我们可以获取网页的源代码。这个执行是“沙盒式”的,它不会去执行网页外的 JavaScript 代码。evalute 方法可以返回一个对象,然而返回值仅限于对象,不能包含函数(或闭包)

var url = 'http://www.qq';
var page = require('webpage').create();
page.open(url, function(status) {
  var title = page.evaluate(function() {
    return document.title;
  });
  console.log('Page title is ' + title);
  phantom.exit();
});

以上代码获取了腾讯的网站标题。

Page title is 腾讯首页

任何来自于网页并且包括来自 evaluate() 内部代码的控制台信息,默认不会显示。

需要重写这个行为,使用 onConsoleMessage 回调函数,示例可以改写成

var url = 'http://www.itcast/';
var page = require('webpage').create();
page.onConsoleMessage = function (msg) {
    console.log(msg);
};
page.open(url, function (status) {
    page.evaluate(function () {
        console.log(document.title);
    });
    phantom.exit();
});

页面自动化处理

  • DOM操作

脚本都是像在浏览器中运行的,所以标准的 JavaScript 的 DOM 操作和 CSS 选择器也是生效的。

例如下面的例子就修改了 User-Agent,然后还返回了页面中某元素的内容。

var url = 'http://www.httpuseragent';
var page = require('webpage').create();
console.log('The default user agent is ' + page.settings.userAgent);
page.settings.userAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36';
page.open('http://www.httpuseragent', function (status) {
    if (status !== 'success') {
        console.log('Unable to access network');
    } 
    else {
        var ua = page.evaluate(function () {
            return document.getElementById('myagent').innerText;
        });
        console.log(ua);
    }
    phantom.exit();
});

运行结果

The default user agent is Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.7 Safari/534.34
Your Http User Agent string is: SpecialAgent

首先打印出了默认的 User-Agent,然后通过修改它,请求验证 User-Agent 的一个站点,通过选择器得到了修改后的 User-Agent。

3、pyppeteer:比 selenium 更高效的爬虫利器

pyppeteer API Reference:https://miyakogi.github.io/pyppeteer/reference.html
pyppeteer github 地址:https://github/miyakogi/pyppeteer
pyppeteer 英文文档地址:https://miyakogi.github.io/pyppeteer/
puppeteer 快速入门:https://blog.csdn/freeking101/article/details/91542887
pyppeteer 进阶技巧:https://wwwblogs/dyfblog/p/10887940.html
爬虫、获取cookie、截屏插件、防爬绕过:https://mohen.blog.csdn/article/details/107312709
爬虫神器 Pyppeteer 的使用:https://blog.csdn/weixin_38819889/article/details/108684254

Pyppeteer  这个项目是非官方的,是基于谷歌官方puppeteer的python版本。chrome 就问题多多,puppeteer也是各种坑,加上pyppeteer是前两者的python版本,也就是产生了只要前两个有一个有bug,那么pyppeteer就会原封不动的继承下来,本来这没什么,但是现在遇到的问题就是 pyppeteer 这个项目已经停止更新,导致很多 bug 根本没人修复。所以,Pyppeteer 已经停止更新,可以使用 playwright-python 代替。

selenium 作为一款知名的 Web 自动化测试框架,selenium 支持多款主流浏览器,提供了功能丰富的API 接口,经常被我们用作爬虫工具来使用。但是 selenium 的缺点也很明显,比如速度太慢、对版本配置要求严苛,最麻烦是经常要更新对应的驱动还有些网页是可以检测到是否是使用了selenium 。并且selenium 所谓的保护机制不允许跨域 cookies 保存以及登录的时候必须先打开网页然后后加载 cookies 再刷新的方式很不友好。

介绍 Pyppeteer 之前先说一下 Puppeteer,Puppeteer 是 Google 基于 Node.js 开发的一个工具,主要是用来操纵 Chrome  浏览器的 API,通过 Javascript 代码来操纵 Chrome 浏览器的一些操作,用作网络爬虫完成数据爬取、Web 程序自动测试等任务。其 API 极其完善,功能非常强大。 而 Pyppeteer 又是什么呢?它实际上是 Puppeteer 的 Python 版本的实现,但他不是 Google 开发的,是一位来自于日本的工程师依据 Puppeteer 的一些功能开发出来的非官方版本。

Pyppeteer 的两大特点:chromium 浏览器 和 asyncio框架。

Pyppeteer 其实是 Puppeteer 的 Python 版本。pyppeteer 模块看不懂就去看puppeteer文档,pyppeteer 只是在 puppeteer之上稍微包装了下而已 。

安装 pyppeteer 库:pip3 install pyppeteer 至于 chromium 浏览器,只需要一条 pyppeteer-install 命令就会自动下载对应的最新版本 chromium 浏览器到 pyppeteer 的默认位置。

window 下 安装完 pyppeteer ,会在 python 安装目录下的 Scripts 目录下 有 pyppeteer-install.exe 和 pyppeteer-install-script.py 两个文件,执行 任意一个都可以安装 chromium 浏览器到 pyppeteer 的默认位置。        

如果不运行 pyppeteer-install 命令,在第一次使用 pyppeteer 的时候也会自动下载并安装 chromium 浏览器,效果是一样的。总的来说,pyppeteer 比起 selenium 省去了 driver 配置的环节。

当然,出于某种原因(需要梯子,或者科学上网),也可能会出现chromium自动安装无法顺利完成的情况,这时可以考虑手动安装:首先,从下列网址中找到自己系统的对应版本,下载chromium压缩包;

'linux': 'https://storage.googleapis/chromium-browser-snapshots/Linux_x64/575458/chrome-linux.zip'
'mac': 'https://storage.googleapis/chromium-browser-snapshots/Mac/575458/chrome-mac.zip'
'win32': 'https://storage.googleapis/chromium-browser-snapshots/Win/575458/chrome-win32.zip'
'win64': 'https://storage.googleapis/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip'

然后,将压缩包放到 pyppeteer 的指定目录下解压缩,windows 系统的默认目录。

其他系统下的默认目录可以参照下面:

  • Windows: C:\Users\<username>\AppData\Local\pyppeteer
  • OS X: /Users/<username>/Library/Application Support/pyppeteer
  • Linux: /home/<username>/.local/share/pyppeteer
    or in $XDG_DATA_HOME/pyppeteer if $XDG_DATA_HOME is defined.

Details see appdirs’s user_data_dir.

好了,安装完成之后我们命令行下测试下:
>>> import pyppeteer
如果没有报错,那么就证明安装成功了。

示例:

执行代码后,手动输入用户名和密码,滑动滑块,可以正常跳转到登录后个人页面。

提示:这个手动滑动滑块有一定的失败几率,有时候失败几率还很高。有时一次就可以滑过,有时好多次都过不去。

示例代码:

import asyncio
from pyppeteer import launch
 
width, height = 1366, 768
 
 
js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}'''
js2 = '''() => {alert(window.navigator.webdriver)}'''
js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }'''
js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}'''
js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''
 
 
async def page_evaluate(page):
    # 替换淘宝在检测浏览时采集的一些参数
    # 需要注意,在测试的过程中发现登陆成功后页面的该属性又会变成True
    # 所以在每次重新加载页面后要重新设置该属性的值。
    await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
 
 
async def main():
    browser = await launch(
        headless=False,
        # userDataDir='./userdata',
        args=['--disable-infobars', f'--window-size={width},{height}', '--no-sandbox']
    )
    page = await browser.newPage()
 
    await page.setViewport(
        {
            "width": width,
            "height": height
        }
    )
    # url = 'https://www.taobao'
    url = 'https://login.taobao/member/login.jhtml'
    await page.goto(url=url)
 
    await page.evaluate(js1)
    await page.evaluate(js3)
    await page.evaluate(js4)
    await page.evaluate(js5)
 
    # await page_evaluate(page)
 
    await asyncio.sleep(100)
    # await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

基础用法

抓取内容  可以使用 xpath 表达式
"""
# Pyppeteer 三种解析方式
    Page.querySelector()      # 选择器
    Page.querySelectorAll()
    Page.xpath()                   # xpath  表达式
# 简写方式为:
    Page.J(), Page.JJ(), and Page.Jx()
"""

import asyncio
from pyppeteer import launch
 
 
async def main():
    # headless参数设为False,则变成有头模式
    # Pyppeteer支持字典和关键字传参,Puppeteer只支持字典传参
    
    # 指定引擎路径
    # exepath = r'C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32/chrome.exe'
    # browser = await launch({'executablePath': exepath, 'headless': False, 'slowMo': 30})
    
    browser = await launch(
        # headless=False,
        {'headless': False}
    )
 
    page = await browser.newPage()
 
    # 设置页面视图大小
    await page.setViewport(viewport={'width': 1280, 'height': 800})
 
    # 是否启用JS,enabled设为False,则无渲染效果
    await page.setJavaScriptEnabled(enabled=True)
    # 超时间见 1000 毫秒
    res = await page.goto('https://www.toutiao/', options={'timeout': 1000})
    resp_headers = res.headers  # 响应头
    resp_status = res.status  # 响应状态
    
    # 等待
    await asyncio.sleep(2)
    # 第二种方法,在while循环里强行查询某元素进行等待
    while not await page.querySelector('.t'):
        pass
    # 滚动到页面底部
    await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')
 
    await asyncio.sleep(2)
    # 截图 保存图片
    await page.screenshot({'path': 'toutiao.png'})
 
    # 打印页面cookies
    print(await page.cookies())
 
    """  打印页面文本 """
    # 获取所有 html 内容
    print(await page.content())
 
    # 在网页上执行js 脚本
    dimensions = await page.evaluate(pageFunction='''() => {
            return {
                width: document.documentElement.clientWidth,  // 页面宽度
                height: document.documentElement.clientHeight,  // 页面高度
                deviceScaleFactor: window.devicePixelRatio,  // 像素比 1.0000000149011612
            }
        }''', force_expr=False)  # force_expr=False  执行的是函数
    print(dimensions)
 
    #  只获取文本  执行 js 脚本  force_expr  为 True 则执行的是表达式
    content = await page.evaluate(pageFunction='document.body.textContent', force_expr=True)
    print(content)
 
    # 打印当前页标题
    print(await page.title())
 
    # 抓取新闻内容  可以使用 xpath 表达式
    """
    # Pyppeteer 三种解析方式
    Page.querySelector()  # 选择器
    Page.querySelectorAll()
    Page.xpath()  # xpath  表达式
    # 简写方式为:
    Page.J(), Page.JJ(), and Page.Jx()
    """
    element = await page.querySelector(".feed-infinite-wrapper > ul>li")  # 纸抓取一个
    print(element)
    # 获取所有文本内容  执行 js
    content = await page.evaluate('(element) => element.textContent', element)
    print(content)
 
    # elements = await page.xpath('//div[@class="title-box"]/a')
    elements = await page.querySelectorAll(".title-box a")
    for item in elements:
        print(await item.getProperty('textContent'))
        # <pyppeteer.execution_context.JSHandle object at 0x000002220E7FE518>
 
        # 获取文本
        title_str = await (await item.getProperty('textContent')).jsonValue()
 
        # 获取链接
        title_link = await (await item.getProperty('href')).jsonValue()
        print(title_str)
        print(title_link)
 
    # 关闭浏览器
    await browser.close()
 
 
asyncio.get_event_loop().run_until_complete(main())
import asyncio
import pyppeteer
from collections import namedtuple
 
headers = {
    'date': 'Sun, 28 Apr 2019 06:50:20 GMT',
    'server': 'Cmcc',
    'x-frame-options': 'SAMEORIGIN\nSAMEORIGIN',
    'last-modified': 'Fri, 26 Apr 2019 09:58:09 GMT',
    'accept-ranges': 'bytes',
    'cache-control': 'max-age=43200',
    'expires': 'Sun, 28 Apr 2019 18:50:20 GMT',
    'vary': 'Accept-Encoding,User-Agent',
    'content-encoding': 'gzip',
    'content-length': '19823',
    'content-type': 'text/html',
    'connection': 'Keep-alive',
    'via': '1.1 ID-0314217270751344 uproxy-17'
}
 
Response = namedtuple("rs", "title url html cookies headers history status")
 
 
async def get_html(url):
    browser = await pyppeteer.launch(headless=True, args=['--no-sandbox'])
    page = await browser.newPage()
    res = await page.goto(url, options={'timeout': 10000})
    data = await page.content()
    title = await page.title()
    resp_cookies = await page.cookies()  # cookie
    resp_headers = res.headers  # 响应头
    resp_status = res.status  # 响应状态
    print(data)
    print(title)
    print(resp_headers)
    print(resp_status)
    return title
 
 
if __name__ == '__main__':
    url_list = [
        "https://www.toutiao",
        "http://jandan/ooxx/page-8#comments",
        "https://www.12306/index"
    ]
    task = [get_html(url) for url in url_list]
 
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*task))
    for res in results:
        print(res)

模拟输入

模拟输入文本:

# 模拟输入 账号密码  {'delay': rand_int()} 为输入时间
await page.type('#TPL_username_1', "sadfasdfasdf")
await page.type('#TPL_password_1', "123456789", )
 
await page.waitFor(1000)
await page.click("#J_SubmitStatic")

使用 tkinter 获取页面高度 宽度

def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height

爬取京东商城

示例代码:

import requests
from bs4 import BeautifulSoup
from pyppeteer import launch
import asyncio
 
 
def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height
 
 
async def main(url):
    # browser = await launch({'headless': False, 'args': ['--no-sandbox'], })
    browser = await launch({'args': ['--no-sandbox'], })
    page = await browser.newPage()
    width, height = screen_size()
    await page.setViewport(viewport={"width": width, "height": height})
    await page.setJavaScriptEnabled(enabled=True)
    await page.setUserAgent(
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
        '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'
    )
    await page.goto(url)
 
    # await asyncio.sleep(2)
 
    await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')
 
    await asyncio.sleep(1)
 
    # content = await page.content()
    li_list = await page.xpath('//*[@id="J_goodsList"]/ul/li')
 
    # print(li_list)
    item_list = []
    for li in li_list:
        a = await li.xpath('.//div[@class="p-img"]/a')
        detail_url = await (await a[0].getProperty("href")).jsonValue()
        promo_words = await (await a[0].getProperty("title")).jsonValue()
        a_ = await li.xpath('.//div[@class="p-commit"]/strong/a')
        p_commit = await (await a_[0].getProperty("textContent")).jsonValue()
        i = await li.xpath('./div/div[3]/strong/i')
        price = await (await i[0].getProperty("textContent")).jsonValue()
        em = await li.xpath('./div/div[4]/a/em')
        title = await (await em[0].getProperty("textContent")).jsonValue()
        item = {
            "title": title,
            "detail_url": detail_url,
            "promo_words": promo_words,
            'p_commit': p_commit,
            'price': price
        }
        item_list.append(item)
        # print(item)
        # break
    # print(content)
 
    await page_close(browser)
    return item_list
 
 
async def page_close(browser):
    for _page in await browser.pages():
        await _page.close()
    await browser.close()
 
 
msg = "手机"
url = "https://search.jd/Search?keyword={}&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq={}&cid2=653&cid3=655&page={}"
 
task_list = []
for i in range(1, 6):
    page = i * 2 - 1
    url = url.format(msg, msg, page)
    task_list.append(main(url))
 
loop = asyncio.get_event_loop()
results = loop.run_until_complete(asyncio.gather(*task_list))
# print(results, len(results))
for i in results:
    print(i, len(i))
 
print('*' * 100)
# soup = BeautifulSoup(content, 'lxml')
# div = soup.find('div', id='J_goodsList')
# for i, li in enumerate(div.find_all('li', class_='gl-item')):
#     if li.select('.p-img a'):
#         print(li.select('.p-img a')[0]['href'], i)
#         print(li.select('.p-price i')[0].get_text(), i)
#         print(li.select('.p-name em')[0].text, i)
#     else:
#         print("#" * 200)
#         print(li)

抓取淘宝

示例代码:

# -*- coding: utf-8 -*-
 
import time
import random
import asyncio
from retrying import retry  # 错误自动重试
from pyppeteer.launcher import launch
 
 
js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}'''
js2 = '''() => {alert(window.navigator.webdriver)}'''
js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }'''
js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}'''
js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''
 
 
def retry_if_result_none(result):
    return result is None
 
 
@retry(retry_on_result=retry_if_result_none, )
async def mouse_slide(page=None):
    await asyncio.sleep(3)
    try:
        await page.hover('#nc_1_n1z')
        await page.mouse.down()
        await page.mouse.move(2000, 0, {'delay': random.randint(1000, 2000)})
        await page.mouse.up()
 
    except Exception as e:
        print(e, '     :slide login False')
        return None
    else:
        await asyncio.sleep(3)
        slider_again = await page.Jeval('.nc-lang-cnt', 'node => node.textContent')
        if slider_again != '验证通过':
            return None
        else:
            await page.screenshot({'path': './headless-slide-result.png'})
            print('验证通过')
            return 1
 
 
def input_time_random():
    return random.randint(100, 151)
 
 
def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height
 
 
async def main(username, pwd, url):
    browser = await launch(
        {'headless': False, 'args': ['--no-sandbox'], },
        userDataDir='./userdata',
        args=['--window-size=1366,768']
    )
    page = await browser.newPage()
    width, height = screen_size()
    await page.setViewport(viewport={"width": width, "height": height})
    await page.setUserAgent(
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
        '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'
    )
 
    await page.goto(url)
    await page.evaluate(js1)
    await page.evaluate(js3)
    await page.evaluate(js4)
    await page.evaluate(js5)
 
    pwd_login = await page.querySelector('.J_Quick2Static')
    # print(await (await pwd_login.getProperty('textContent')).jsonValue())
    await pwd_login.click()
 
    await page.type('#TPL_username_1', username, {'delay': input_time_random() - 50})
    await page.type('#TPL_password_1', pwd, {'delay': input_time_random()})
 
    await page.screenshot({'path': './headless-test-result.png'})
    time.sleep(2)
 
    slider = await page.Jeval('#nocaptcha', 'node => node.style')  # 是否有滑块
 
    if slider:
        print('出现滑块情况判定')
        await page.screenshot({'path': './headless-login-slide.png'})
        flag = await mouse_slide(page=page)
        if flag:
            print(page.url)
            await page.keyboard.press('Enter')
            await get_cookie(page)
    else:
        await page.keyboard.press('Enter')
        await page.waitFor(20)
        await page.waitForNavigation()
        try:
            global error
            error = await page.Jeval('.error', 'node => node.textContent')
        except Exception as e:
            error = None
            print(e, "错啦")
        finally:
            if error:
                print('确保账户安全重新入输入')
            else:
                print(page.url)
                # 可继续网页跳转 已经携带 cookie
                # await get_search(page)
                await get_cookie(page)
    await page_close(browser)
 
 
async def page_close(browser):
    for _page in await browser.pages():
        await _page.close()
    await browser.close()
 
 
async def get_search(page):
    # https://s.taobao/search?q={查询的条件}&p4ppushleft=1%2C48&s={每页 44 条 第一页 0 第二页 44}&sort=sale-desc
    await page.goto("https://s.taobao/search?q=气球")
 
    await asyncio.sleep(5)
    # print(await page.content())
 
 
# 获取登录后cookie
async def get_cookie(page):
    res = await page.content()
    cookies_list = await page.cookies()
    cookies = ''
    for cookie in cookies_list:
        str_cookie = '{0}={1};'
        str_cookie = str_cookie.format(cookie.get('name'), cookie.get('value'))
        cookies += str_cookie
    print(cookies)
    # 将cookie 放入 cookie 池 以便多次请求 封账号 利用cookie 对搜索内容进行爬取
    return cookies
 
 
if __name__ == '__main__':
    tb_username = '淘宝用户名'
    tb_pwd = '淘宝密码'
    tb_url = "https://login.taobao/member/login.jhtml"
 
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(tb_username, tb_pwd, tb_url))

利用 上面 获取到的 cookie 爬取搜索内容

示例代码:

import json
import requests
import re
 
# 设置 cookie 池 随机发送请求 通过 pyppeteer 获取 cookie
cookie = '_tb_token_=edd7e354dee53;t=fed8f4ca1946ca1e73223cfae04bc589;sg=20f;cna=2uJSFdQGmDMCAbfFWXWAC4Jv;cookie2=1db6cd63ad358170ea13319f7a862c33;_l_g_=Ug%3D%3D;v=0;unb=3150916610;skt=49cbfd5e01d1b550;cookie1=BxVRmD3sh19TaAU6lH88bHw5oq%2BgcAGcRe229Hj5DTA%3D;csg=cf45a9e2;uc3=vt3=F8dByEazRMnQZDe%2F9qI%3D&id2=UNGTqfZ61Z3rsA%3D%3D&nk2=oicxO%2BHX4Pg%3D&lg2=U%2BGCWk%2F75gdr5Q%3D%3D;existShop=MTU1Njg3MDM3MA%3D%3D;tracknick=%5Cu7433150322;lgc=%5Cu7433150322;_cc_=V32FPkk%2Fhw%3D%3D;mt=ci=86_1;dnk=%5Cu7433150322;_nk_=%5Cu7433150322;cookie17=UNGTqfZ61Z3rsA%3D%3D;tg=0;enc=tThHs6Sn3BAl8v1fu3J4tMpgzA1n%2BLzxjib0vDAtGsXJCb4hqQZ7Z9fHIzsN0WghdcKEsoeKz6mBwPUpyzLOZw%3D%3D;JSESSIONID=B3F383B3467EC60F8CA425935232D395;l=bBMspAhrveV5732DBOCanurza77OSIRYYuPzaNbMi_5pm6T_G4QOlC03xF96VjfRswYBqh6Mygv9-etuZ;hng=CN%7Czh-CN%7CCNY%7C156;isg=BLi41Q8PENDal3xUVsA-aPbfiWaKiRzB6vcTu_IpBPOmDVj3mjHsO86vxUQYW9SD;uc1=cookie16=W5iHLLyFPlMGbLDwA%2BdvAGZqLg%3D%3D&cookie21=W5iHLLyFeYZ1WM9hVnmS&cookie15=UIHiLt3xD8xYTw%3D%3D&existShop=false&pas=0&cookie14=UoTZ4ttqLhxJww%3D%3D&tag=8&lng=zh_CN;thw=cn;x=e%3D1%26p%3D*%26s%3D0%26c%3D0%26f%3D0%26g%3D0%26t%3D0;swfstore=34617;'
 
headers = {
    'cookie': cookie,
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
 
rep = requests.get('https://s.taobao/search?q=手机&p4ppushleft=1%2C48&s=0&sort=sale-desc ', headers=headers)
rep.encoding = 'utf-8'
res = rep.text
print(res)
 
r = repile(r'g_page_config = (.*?)g_srp_loadCss', re.S)
res = r.findall(res)
 
data = res[0].strip().rstrip(';')
dic_data = json.loads(data)
auctions = dic_data.get('mods')['itemlist']['data']['auctions']
 
# print(auctions,len(auctions))
for item in auctions[1:]:
    print(item)
    break

针对 iframe 的操作

  • page.frames 获取所有的 iframe 列表 需要判断操作的是哪一个 iframe 跟操作 page 一样操作
from pyppeteer import launch
import asyncio
 
 
async def main(url):
    w = await launch({'headless': False, 'args': ['--no-sandbox'], })
 
    page = await w.newPage()
    await page.setViewport({"width": 1366, 'height': 800})
    await page.goto(url)
    try:
        await asyncio.sleep(1)
 
        frame = page.frames
        print(frame)  # 需要找到是哪一个 frame
        title = await frame[1].title()
        print(title)
        await asyncio.sleep(1)
        login = await frame[1].querySelector('#switcher_plogin')
        print(login)
        await login.click()
 
        await asyncio.sleep(20)
    except Exception as e:
        print(e, "EEEEEEEEE")
 
    for _page in await w.pages():
        await _page.close()
    await w.close()
 
 
asyncio.get_event_loop().run_until_complete(main("https://i.qq/?rd=1"))
# asyncio.get_event_loop().run_until_complete(main("https://www.gushici/"))

与 scrapy 的整合

加入downloadmiddleware

from scrapy import signals
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
import random
import pyppeteer
import asyncio
import os
from scrapy.http import HtmlResponse
 
pyppeteer.DEBUG = False 
 
class FundscrapyDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.
    def __init__(self) :
        print("Init downloaderMiddleware use pypputeer.")
        os.environ['PYPPETEER_CHROMIUM_REVISION'] ='588429'
        # pyppeteer.DEBUG = False
        print(os.environ.get('PYPPETEER_CHROMIUM_REVISION'))
        loop = asyncio.get_event_loop()
        task = asyncio.ensure_future(self.getbrowser())
        loop.run_until_complete(task)
 
        #self.browser = task.result()
        print(self.browser)
        print(self.page)
        # self.page = await browser.newPage()
    async def getbrowser(self):
        self.browser = await pyppeteer.launch()
        self.page = await self.browser.newPage()
        # return await pyppeteer.launch()
    async def getnewpage(self): 
        return  await self.browser.newPage()
 
    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s
 
    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.
 
        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        loop = asyncio.get_event_loop()
        task = asyncio.ensure_future(self.usePypuppeteer(request))
        loop.run_until_complete(task)
        # return task.result()
        return HtmlResponse(url=request.url, body=task.result(), encoding="utf-8",request=request)
 
    async def usePypuppeteer(self, request):
        print(request.url)
        # page = await self.browser.newPage()
        await self.page.goto(request.url)
        content = await self.page.content()
        return content 
 
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.
 
        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response
 
    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.
 
        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass
 
    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

实战 异步爬取

示例 1 :快速上手

接下来我们测试下基本的页面渲染操作,这里我们选用的网址为:http://quotes.toscrape/js/,这个页面是 JavaScript 渲染而成的,用基本的 requests 库请求得到的 HTML 结果里面是不包含页面中所见的条目内容的。

为了证明 requests 无法完成正常的抓取,我们可以先用如下代码来测试一下:

import requests
from pyquery import PyQuery as pq
 
url = 'http://quotes.toscrape/js/'
response = requests.get(url=url)
doc = pq(response.text)
print('Quotes : {0}'.format(doc('.quote').length))
 
# 结果
# Quotes : 0

这里首先使用 requests 来请求网页内容,然后使用 pyquery 来解析页面中的每一个条目。观察源码之后我们发现每个条目的 class 名为 quote,所以这里选用了 .quote 这个 CSS 选择器来选择,最后输出条目数量。

运行结果:Quotes: 0

结果是 0,这就证明使用 requests 是无法正常抓取到相关数据的。

为什么?

因为这个页面是 JavaScript 渲染而成的,我们所看到的内容都是网页加载后又执行了 JavaScript 之后才呈现出来的,因此这些条目数据并不存在于原始 HTML 代码中,而 requests 仅仅抓取的是原始 HTML 代码。

好的,所以遇到这种类型的网站我们应该怎么办呢?

其实答案有很多:

  1. 分析网页源代码数据,如果数据是隐藏在 HTML 中的其他地方,以 JavaScript 变量的形式存在,直接提取就好了。
  2. 分析 Ajax,很多数据可能是经过 Ajax 请求时候获取的,所以可以分析其接口。
  3. 模拟 JavaScript 渲染过程,直接抓取渲染后的结果。

而 Pyppeteer 和 Selenium 就是用的第三种方法,下面我们再用 Pyppeteer 来试试,如果用 Pyppeteer 实现如上页面的抓取的话,代码就可以写为如下形式:

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq
 
 
async def main():
    browser = await launch()
    page = await browser.newPage()
    url = 'http://quotes.toscrape/js/'
    await page.goto(url=url)
    doc = pq(await page.content())
    print('Quotes : {0}'.format(doc('.quote').length))
    await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

运行结果:Quotes: 10
看运行结果,这说明我们就成功匹配出来了 class 为 quote 的条目,总数为 10 条,具体的内容可以进一步使用 pyquery 解析查看。

那么这里面的过程发生了什么?

实际上,Pyppeteer 整个流程就完成了浏览器的开启、新建页面、页面加载等操作。另外 Pyppeteer 里面进行了异步操作,所以需要配合 async/await 关键词来实现。首先, launch 方法会新建一个 Browser 对象,然后赋值给 browser,然后调用 newPage 方法相当于浏览器中新建了一个选项卡,同时新建了一个 Page 对象。然后 Page 对象调用了 goto 方法就相当于在浏览器中输入了这个 URL,浏览器跳转到了对应的页面进行加载,加载完成之后再调用 content 方法,返回当前浏览器页面的源代码。然后进一步地,我们用 pyquery 进行同样地解析,就可以得到 JavaScript 渲染的结果了。另外其他的一些方法如调用 asyncio 的 get_event_loop 等方法的相关操作则属于 Python 异步 async 相关的内容了,大家如果不熟悉可以了解下 Python 的 async/await 的相关知识。好,通过上面的代码,我们就可以完成 JavaScript 渲染页面的爬取了。

模拟网页截图,保存 PDF,执行自定义的 JavaScript 获得特定的内容

接下来我们再看看另外一个例子,这个例子可以模拟网页截图,保存 PDF,另外还可以执行自定义的 JavaScript 获得特定的内容,代码如下:

import asyncio
from pyppeteer import launch
 
 
async def main():
    browser = await launch()
    page = await browser.newPage()
    url = 'http://quotes.toscrape/js/'
    await page.goto(url=url)
    await page.screenshot(path='test_screenshot.png')
    await page.pdf(path='test_pdf.pdf')
 
    # 在网页上执行js 脚本
    dimensions = await page.evaluate(pageFunction='''() => {
                return {
                    width: document.documentElement.clientWidth,    // 页面宽度
                    height: document.documentElement.clientHeight,  // 页面高度
                    deviceScaleFactor: window.devicePixelRatio,     // 像素比 1.0000000149011612
                }
            }''', force_expr=False)  # force_expr=False  执行的是函数
 
    print(dimensions)
    await browser.close()
 
 
asyncio.get_event_loop().run_until_complete(main())
 
# 结果
# {'width': 800, 'height': 600, 'deviceScaleFactor': 1}

这里我们又用到了几个新的 API,完成了网页截图保存、网页导出 PDF 保存、执行 JavaScript 并返回对应数据。

 evaluate 方法执行了一些 JavaScript,JavaScript 传入的是一个函数,使用 return 方法返回了网页的宽高、像素大小比率三个值,最后得到的是一个 JSON 格式的对象。

总之利用 Pyppeteer 我们可以控制浏览器执行几乎所有动作,想要的操作和功能基本都可以实现,用它来自由地控制爬虫当然就不在话下了。

了解了基本的实例之后,我们再来梳理一下 Pyppeteer 的一些基本和常用操作。Pyppeteer 的几乎所有功能都能在其官方文档的 API Reference 里面找到,链接为:API Reference — Pyppeteer 0.0.25 documentation,用到哪个方法就来这里查询就好了,参数不必死记硬背,即用即查就好。

登录淘宝 (打开网页后,手动输入用户名和密码,可以看到正常跳转到登录后的页面):

import asyncio
from pyppeteer import launch
 
width, height = 1366, 768
 
 
js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}'''
js2 = '''() => {alert(window.navigator.webdriver)}'''
js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }'''
js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}'''
js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''
 
 
async def page_evaluate(page):
    # 替换淘宝在检测浏览时采集的一些参数
    # 需要注意,在测试的过程中发现登陆成功后页面的该属性又会变成True
    # 所以在每次重新加载页面后要重新设置该属性的值。
    await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
 
 
async def main():
    browser = await launch(
        headless=False,
        # userDataDir='./userdata',
        args=['--disable-infobars', f'--window-size={width},{height}', '--no-sandbox']
    )
    page = await browser.newPage()
 
    await page.setViewport(
        {
            "width": width,
            "height": height
        }
    )
    url = 'https://www.taobao'
    await page.goto(url=url)
 
    # await page.evaluate(js1)
    # await page.evaluate(js3)
    # await page.evaluate(js4)
    # await page.evaluate(js5)
 
    await page_evaluate(page)
 
    await asyncio.sleep(100)
    # await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

如果把上面 js 去掉,发现淘宝可以检测出来, 跳转不到登录后的页面。

window.navigator 对象包含有关访问者浏览器的信息:https://www.runoob/js/js-window-navigator.html

js 主要需要修改浏览器的 window.navigator.webdriver、window.navigator.languages等值。

打开正常的浏览器可以看到:

window.navigator.webdriver的值为undefined,而通过pyppeteer控制打开的浏览器该值为True,当被检测到该值为True的时候,则滑动会一直失败,所以我们需要修改该属性。需要注意,在测试的过程中发现登陆成功后页面的该属性又会变成True,所以在每次重新加载页面后要重新设置该属性的值。

async def page_evaluate(page):
    # 替换淘宝在检测浏览时采集的一些参数
    await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
    await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')

另一种方法可以进一步免去淘宝登录的烦恼,那就是设置用户目录。

平时我们已经注意到,当我们登录淘宝之后,如果下次再次打开浏览器发现还是登录的状态。这是因为淘宝的一些关键 Cookies 已经保存到本地了,下次登录的时候可以直接读取并保持登录状态。

那么这些信息保存在哪里了呢?其实就是保存在用户目录下了,里面不仅包含了浏览器的基本配置信息,还有一些 Cache、Cookies 等各种信息都在里面,如果我们能在浏览器启动的时候读取这些信息,那么启动的时候就可以恢复一些历史记录甚至一些登录状态信息了。

这也就解决了一个问题:很多朋友在每次启动 Selenium 或 Pyppeteer 的时候总是是一个全新的浏览器,那就是没有设置用户目录,如果设置了它,每次打开就不再是一个全新的浏览器了,它可以恢复之前的历史记录,也可以恢复很多网站的登录信息。

当然可能时间太久了,Cookies 都过期了,那还是需要登录的。

那么这个怎么来做呢?很简单,在启动的时候设置 userDataDir 就好了,示例如下:

 browser = await launch(
        headless=False,
        userDataDir='./userdata',
        args=['--disable-infobars', f'--window-size={width},{height}']
    )

用户文件夹

具体的介绍可以看官方的一些说明,如:https://chromium.googlesource/chromium/src/+/master/docs/user_data_dir.md 这里面介绍了 userdatadir 的相关内容。

命令行启动 chrome 并进入指定的 URL:chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://login.taobao/member/login.jhtml

执行完后会打开 淘宝的登录页面,登录淘宝,然后保存用户名密码,这样登录信息就保存在 userdatadir 目录下了

在执行 chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://www.taobao

可以看到已经时登录状态了。

爬取今日头条

# -*- coding: utf-8 -*-
# @Author  : 
# @File    : toutiao.py
# @Software: PyCharm
# @description : XXX
 
 
import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq
 
 
def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height
 
 
async def main():
    width, height = screen_size()
    print(f'screen : [ width:{width} , height:{height} ]')
 
    browser = await launch(headless=False, args=[f'--window-size={width},{height}'])
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
 
    # 是否启用JS,enabled设为False,则无渲染效果
    await page.setJavaScriptEnabled(enabled=True)
 
    await page.goto('https://www.toutiao')
    await asyncio.sleep(5)
 
    print(await page.cookies())  # 打印页面cookies
    print(await page.content())  # 打印页面文本
    print(await page.title())    # 打印当前页标题
 
    # 抓取新闻标题
    title_elements = await page.xpath('//div[@class="title-box"]/a')
    for item in title_elements:
        # 获取文本
        title_str = await (await item.getProperty('textContent')).jsonValue()
        print(await item.getProperty('textContent'))
        # 获取链接
        title_link = await (await item.getProperty('href')).jsonValue()
        print(title_str)
        print(title_link)
 
    # 在搜索框中输入python
    await page.type('input.tt-input__inner', 'python')
 
    # 点击搜索按钮
    await page.click('button.tt-button')
    await asyncio.sleep(5)
 
    # print(page.url)
    # 今日头条点击后新开一个页面, 通过打印url可以看出page还停留在原页面
    # 以下用于切换至新页面
    pages = await browser.pages()
    page = pages[-1]
    # print(page.url)
 
    page_source = await page.content()
    text = pq(page_source)
    await page.goto(
        url="https://www.toutiao/api/search/content/?"
            "aid=24&app_name=web_search&offset=60&format=json"
            "&keyword=python&autoload=true&count=20&en_qc=1"
            "&cur_tab=1&from=search_tab&pd=synthesis&timestamp=1555589585193"
    )
    for i in range(1, 10):
        print(text("#J_section_{} > div > div > div.normal.rbox > div > div.title-box > a > span".format(i)).text())
 
    # 关闭浏览器
    await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())
 

4、selenium 反检测

利用 Chrome DevTools 协议

Chrome DevTools Protocol (协议详细内容):https://chromedevtools.github.io/devtools-protocol/

之前淘宝对于 selenium 还是很友好的,后来 selenium 被检测了 window.navigator.webdriver 等参数,出滑动验证码什么的,selenium 已经很难用了, 

利用 Chrome DevTools 协议。它允许客户 检查 和 调试 Chrome 浏览器。

在 系统环境变量 PATH 里将 chrome的路径 添加进去。

打开cmd,在命令行中输入命令:chrome.exe --remote-debugging-port=9999 --user-data-dir="C:\selenum\AutomationProfile"

对于-remote-debugging-port 值,可以指定任何打开的端口。

对于-user-data-dir 标记,指定创建新 Chrome 配置文件的目录。它是为了确保在单独的配置文件中启动 chrome,不会污染你的默认配置文件。

执行完命令后,会打开一个浏览器页面,我们输入淘宝网址(https://login.taobao/member/login.jhtml),输入用户名和密码,登录淘宝后用户信息就保存在 --user-data-dir="C:\selenum\AutomationProfile" 所指定的文件夹中。

执行 js window.open() 打不开窗口时,是因为 chrome 默认不允许弹出窗口,改下 chrome 设置就可以了
在 chrome 浏览器地址栏输入:chrome://settings/content/popups,把 已阻止(推荐)  改成 允许 即可。
或者 chrome -》设置 -》高级 -》隐私设置和安全性 -》网站设置 -》弹出式窗口和重定向,也可以设置。

不要关闭上面浏览器,然后执行 python 代码。在淘宝搜索 "电脑" 关键字,并打印前 5 页 所有 搜索内容

import os
import time
import random
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdrivermon.by import By
from selenium.webdriver.support import expected_conditions as EC
 
# from selenium.webdrivermon.action_chains import ActionChains
 
 
def main():        
    # os.system(r'C:\Users\Administrator\AppData\Local\Google\Chrome\Application/chrome.exe --remote-debugging-port=9999 --user-data-dir="C:\selenum\AutomationProfile"')
    chrome_debug_port = 9999
    chrome_options = Options()
    # chrome_options.add_argument('--headless')
    chrome_options.add_experimental_option("debuggerAddress", f"127.0.0.1:{chrome_debug_port}")
 
    browser = webdriver.Chrome(chrome_options=chrome_options)
    wait = WebDriverWait(browser, 5)
    print(browser.title)
 
    # 当前句柄
    current_handle = browser.current_window_handle
 
    # browser.execute_script('window.open("https://login.taobao/member/login.jhtml")')
    browser.execute_script('window.open("http://www.baidu")')
 
    # 所有句柄
    all_handle = browser.window_handles
    second_handle = all_handle[-1]
 
    # 切回first
    browser.switch_to.window(current_handle)
 
    url = 'https://s.taobao/search?q=电脑'
    browser.get(url)
 
    produce_info_xpath = '//div[contains(@class, "J_MouserOnverReq")]//div[@class="row row-2 title"]/a'
    produce_info = browser.find_elements_by_xpath(produce_info_xpath)
    for produce in produce_info:
        print(produce.text.replace(' ', ''))
 
    # 这里是演示,所以只爬了前 5 页
    for page_num in range(2, 6):
        next_page_xpath = '//li[@class="item next"]'
        next_page = browser.find_element_by_xpath(next_page_xpath)
        next_page_enable = False if 'disabled' in next_page.get_attribute('class') else True
        if next_page_enable:
            print('*' * 100)
            print(f'第 {page_num} 页')
            next_page.click()
            # browser.refresh()
            produce_info_xpath = '//div[contains(@class, "J_MouserOnverReq")]//div[@class="row row-2 title"]/a'
 
            wait.until(EC.presence_of_all_elements_located((By.XPATH, produce_info_xpath)))
            time.sleep(random.randint(3, 5))
            produce_info = browser.find_elements_by_xpath(produce_info_xpath)
            for produce in produce_info:
                print(produce.text.replace(' ', ''))
        else:
            break
 
 
if __name__ == '__main__':
    main()
 

代码 2(根据关键字搜索,然后抓取 店铺名,店铺地址,店铺电话,):

# -*- coding: utf-8 -*-
 
 
import time
import random
import parsel
import re
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdrivermon.by import By
from selenium.webdriver.support import expected_conditions as EC
 
 
# from selenium.webdrivermon.action_chains import ActionChains
 
class TaoBaoSearch(object):
    def __init__(self):
        super(TaoBaoSearch, self).__init__()
        self.browser = None
        self.wait = None
        self.master_handler = None
        self.slaver_handler = None
        self.temp = None
        self.browser_init()
 
    def browser_init(self):
        chrome_debug_port = 9999
        chrome_options = Options()
        chrome_options.add_experimental_option("debuggerAddress", f"127.0.0.1:{chrome_debug_port}")
        # chrome_options.add_argument('--headless')
 
        self.browser = webdriver.Chrome(chrome_options=chrome_options)
        self.wait = WebDriverWait(self.browser, 5)
 
        all_handler = self.browser.window_handles
        if len(all_handler) >= 1:
            for index in all_handler[1:]:
                self.browser.switch_to.window(index)
                self.browser.close()
 
        # self.master_handler = self.browser.current_window_handle
        self.master_handler = self.browser.window_handles[0]
 
        self.browser.switch_to.window(self.master_handler)
        self.browser.execute_script('window.open()')
        # self.browser.execute_script('window.open("_blank")')
        handlers = self.browser.window_handles
        self.slaver_handler = handlers[-1]
        # print(self.browser.title)
 
    def get_detail_info(self, shop_url=None):
        # 切换到 从 窗口
        self.browser.switch_to.window(self.slaver_handler)
        self.browser.get(shop_url)
        html = self.browser.page_source
        html = html.replace('&lt;', '<').replace('&gt;', '>')
        # print(html)
        s_html = parsel.Selector(text=html)
        shop_keeper_xpath = '//div[@class="extend"]//li[@class="shopkeeper"]//a/text()'
        shop_keeper = s_html.xpath(shop_keeper_xpath).extract_first()
 
        phone_reg = '联系电话:(\d+-?\d+)|联系手机:(\d+)'
        phone = re.findall(phone_reg, html)
        # 处理完后 一定要切换到 主 窗口
        self.browser.switch_to.window(self.master_handler)
        return shop_keeper, phone
 
    def process_item(self, item):
        self.temp = None
        shop_xpath = './/div[@class="shop"]//a'
        local_xpath = './/div[@class="location"]'
        shop = item.find_element_by_xpath(shop_xpath).text
        shop_url = item.find_element_by_xpath(shop_xpath).get_attribute('href')
        local = item.find_element_by_xpath(local_xpath).text
        shop_keeper, phone = self.get_detail_info(shop_url)
        if phone:
            print(f'shop : {shop}')
            print(f'local : {local}')
            print(f'shop_url : {shop_url}')
            print(f'shop_keeper : {shop_keeper}')
            print(f'phone : {phone}')
            with open('./info.txt', 'a+') as f:
                f.write(shop + ',')
                f.write(local + ',')
                f.write(shop_url + ',')
                f.write(shop_keeper + ',')
                f.write(f'{phone}')
                f.write('\n')
 
    def main(self):
        # 切回 主 窗口
        self.browser.switch_to.window(self.master_handler)
        key_word = input('输入淘宝搜索关键字:')
        if not key_word:
            print('没有输入关键字。默认搜索 “手机”')
            key_word = '手机'
        url = f'https://s.taobao/search?q={key_word}'
        self.browser.get(url)
        shop_and_local_xpath = '//div[contains(@class, "J_MouserOnverReq")]//div[@class="row row-3 g-clearfix"]'
        shop_and_local = self.browser.find_elements_by_xpath(shop_and_local_xpath)
        for item in shop_and_local:
            self.process_item(item)
 
        # 这里是演示,所以只爬了前 5 页
        for page_num in range(2, 6):
            next_page_xpath = '//li[@class="item next"]'
            next_page = self.browser.find_element_by_xpath(next_page_xpath)
            next_page_enable = False if 'disabled' in next_page.get_attribute('class') else True
            if next_page_enable:
                print('*' * 100)
                print(f'第 {page_num} 页')
                next_page.click()
                # self.browser.refresh()
                self.wait.until(EC.presence_of_all_elements_located((By.XPATH, shop_and_local_xpath)))
                time.sleep(random.randint(3, 5))
                shop_and_local = self.browser.find_elements_by_xpath(shop_and_local_xpath)
                for item in shop_and_local:
                    self.process_item(item)
            else:
                break
 
 
if __name__ == '__main__':
    tb = TaoBaoSearch()
    tb.main()

headless 模式 

上面是一直有浏览器窗口的,没法使用 无头模式,可以使用 --user-data-dir 参数,然后设置无头模式。如果想改变 Chrome 位置,可以设置  chrome_options.binary_location 为 chrome.exe 路径即可。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
if __name__ == '__main__':
 
    chrome_options = Options()
 
    # 不使用默认的Chrome安装版本时,可以设置binary_location 指定 Chrome 路径 。
    # chrome 和 Chromium 对应 chromedriver.exe 版本不一样
    chrome_options.binary_location = r'D:\chrome\chrome.exe'
    # chrome_options.binary_location = r'D:\Chromium\chrome.exe'
 
    # chrome_options.add_argument('--headless')
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument('disable-infobars')
    chrome_options.add_argument(r'--user-data-dir=D:\chrome\userdatadir')
    # chrome_options.add_argument(r'--user-data-dir=D:\Chromium\userdatadir')
 
    browser = webdriver.Chrome(
        chrome_options=chrome_options,
        executable_path=r'D:\chrome\chromedriver.exe'
        # executable_path=r'D:\Chromium\chromedriver.exe'
    )
 
    browser.get('https://www.taobao/')
    user_name_xpath = '//div[@class="site-nav-user"]/a'
    user_name = browser.find_element_by_xpath(user_name_xpath).text
    print(user_name)
 

可以看到 无头模式下,使用 --user-data-dir 参数,可以登录淘宝。前提需要先手动登录淘宝,拿到登录信息的文件夹。

ichrome

github 地址:https://github/ClericPy/ichrome

这里就不放天猫、淘宝的代码了,贴一个药监局的:

( 流程:药品  --->  药品查询  --->  国产药品 ,然后就一直翻页)

import asyncio
from lxml import etree
from ichrome import AsyncChromeDaemon
 
 
async def main():
    async with AsyncChromeDaemon(headless=0, disable_image=False) as cd:
        async with cd.connect_tab(index=0, auto_close=True) as tab:
            url = 'https://www.nmpa.gov/yaopin/index.html'
            wait_timeout = 5
            await tab.goto(url, timeout=wait_timeout)
            await asyncio.sleep(2)
 
            data_query_css_string = '#layer3 > div > a:nth-child(9)'
            await tab.wait_tag(data_query_css_string, max_wait_time=wait_timeout)
            await tab.click(data_query_css_string, timeout=wait_timeout)
            await asyncio.sleep(2)
 
            yao_query_css_string = '[title="国家局批准的药品批准文号信息"]'
            await tab.wait_tag(yao_query_css_string, max_wait_time=wait_timeout)
            await tab.click(yao_query_css_string, timeout=wait_timeout)
            await asyncio.sleep(2)
 
            while True:
                data_link_css_string = '#content table:nth-child(2) > tbody > tr:nth-child(1) > td > p > a'
                await tab.wait_tag(data_link_css_string, timeout=wait_timeout)
 
                html = await tab.get_html(timeout=wait_timeout)
                s_html = etree.HTML(text=html)
                s_table = s_html.xpath('//div[@id="content"]//table')[2]
                s_tr_list = s_table.xpath('.//tr')
                for s_tr in s_tr_list:
                    tag_a = s_tr.xpath('string(.)').strip()
                    print(tag_a)
                    # tag_a_href = s_tr.xpath('.//a/@href')
                    # print(tag_a_href)
 
                btn_next = '[src="images/dataanniu_07.gif"]'
                await tab.click(btn_next, timeout=wait_timeout)
                await asyncio.sleep(2)
 
 
if __name__ == "__main__":
    asyncio.run(main())

chrome 多开:设置不同的 debug_port 和 user_data_dir 可以达到多开 Chrome 

import json
import asyncio
import aiomultiprocess
from loguru import logger
from ichrome import AsyncChromeDaemon
from ichrome.async_utils import Chrome
 
 
async def startup_chrome(dp_port=None):
    """
        设置 chrome 参数,然后启动 chrome
    :param dp_port: 自定义 debug port
    :return:
    """
    logger.info(f'dp_port ---> {dp_port}')
    timeout = 5
    # 也可以给 Chrome 添加代理
    proxy = '127.0.0.1:8080'
    udd= f'c:/chrome_user_data_dir_{dp_port}'
    async with AsyncChromeDaemon(port=dp_port, proxy=proxy, user_data_dir=udd) as cd:
        async with cd.connect_tab(index=0) as tab:
            url = 'https://space.bilibili/1904149/'
            await tab.set_url(url, timeout=timeout)
            await asyncio.sleep(5)
            cookie = await tab.get_cookies(url, timeout=timeout)
            cookie_string = json.dumps(cookie, ensure_ascii=False)
            logger.info(f'cookie_string ---> {cookie_string}')
 
 
async def main():
    db_list = [9301 + offset for offset in range(5)]
    async with aiomultiprocess.Pool() as aio_pool:
        await aio_pool.map(startup_chrome, db_list)
    await aio_pool.join()
 
 
if __name__ == "__main__":
    asyncio.run(main())
    pass

undetected_chromedrive

github:https://github/search?q=undetected-chromedriver

github 搜索 selenium,看看还有没有其他的

pip install  git+https://github/ultrafunkamsterdam/undetected-chromedriver.git

简单的例子 ,懂车帝对selenium反爬挺厉害,通过undetected_chromedriver可轻松搞定。

import undetected_chromedriver as uc
import ssl

ssl._create_default_https_context = ssl._create_unverified_context

uc.TARGET_VERSION = 91
driver = uc.Chrome()
driver.get('https://www.dongchedi/user/53334173333')
driver.find_element_by_xpath('//*[@id="__next"]/div[1]/div[2]/div/div[1]/div[2]/header/nav/a[2]').click()
driver.close()

5、Playwright

Playwright 官网文档

github 搜索:https://github/search?q=Playwright

Playwright

  • :https://github/microsoft/playwright
  • :https://github/microsoft/playwright-python

scrapy-playwright:https://github/scrapy-plugins/scrapy-playwright

官网文档 ( docs、API ):https://playwright.dev/python/docs/intro

特点

  • 支持所有浏览器。Playwright 可以在所有浏览器中实现快速、可靠和强大的自动化测
  • 快速可靠的执行
  • 强大的自动化功能
  • 与你的工作流集成

为什么选择 Playwright

:https://wwwblogs/fnng/p/14274960.html

Python 示例代码

基于 playwright 和 pytest 单元测试框架的自动化项目

:https://github/defnngj/playwright-pro

同步 代码 示例

from playwright.sync_api import sync_playwright

def run(playwright):
    firefox = playwright.firefox
    browser = firefox.launch()
    page = browser.new_page()
    page.goto("https://example")
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

playwright 支持创建多个Browser contexts,相当于是打开浏览器后,可以创建多个页面上下文,每个上下文做的操作可以不同

from playwright.sync_api import sync_playwright

# 打开两个浏览器上下文
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=100)  # 打开浏览器
    context1 = browser.new_context()  # 创建浏览器上下文,支持创建多个上下文
    page1 = context1.new_page()  # 新打开一个浏览器标签页
    page1.goto("https://www.baidu")
    context2 = browser.new_context()  # 创建浏览器上下文,支持创建多个上下文
    page2 = context2.new_page()  # 新打开一个浏览器标签页
    page2.goto("https://www.bilibili")
    browser.close()

运行效果如图。打开两个浏览器实例,

一个浏览器上下文就相当于一个 浏览器 实例,浏览器和上下文都可以使用 new_page() 方法打开一个新的浏览器标签页(选项卡)

browser = p.chromium.launch(headless=False)
page = browser.new_page()

当我们通过点击某些按钮/超链接打开一个新的浏览器标签页时,还需要继续在这个浏览器标签页上继续操作时,那么可以使用以下方式

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=100)  # 打开浏览器
    context1 = browser.new_context()  # 创建浏览器上下文,支持创建多个上下文
    page1 = context1.new_page()
    page1.goto("https://www.baidu")
    with context1.expect_page() as new_page_info:
        page1.click('//a[contains(@href, "https://www.hao123")]')  # 在百度首页点击hao123后会打开一个新的选项卡
    new_page = new_page_info.value
    new_page.click('//a[contains(text(), "hao123推荐")]')  # 在hao123点击hao123推荐
pass

参考:https://playwright.dev/python/docs/multi-pages

异步 代码 示例

import asyncio
from playwright.async_api import async_playwright

async def run(playwright):
    firefox = playwright.firefox
    browser = await firefox.launch()
    page = await browser.new_page()
    await page.goto("https://example")
    await browser.close()

async def main():
    async with async_playwright() as playwright:
        await run(playwright)
asyncio.run(main())

新兴爬虫利器 Playwright 的基本用法

:https://cuiqingcai/36045.html

playwright 教程

:https://blog.csdn/m0_51156601/article/details/126886040

更多推荐

爬虫教程( 5 ) --- Selenium、PhantomJS、selenium反检测、Playwright、Playwright-python、scrapy

本文发布于:2023-06-14 09:47:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1462689.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:爬虫   教程   PhantomJS   Selenium   selenium

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!