JavaScript动态页面的爬取「爬虫」

JavaScript动态渲染页面爬取

博客源地址:修能的博客

复杂的页面动态渲染爬取难题

Ajax数据的分析和爬取方式,其实也是JavaAScript动态渲染页面的一种情形,通过直接分析Ajax

,可以让requestsurllib也可以来爬取数据。

但是Ajax也只是JavaAScript的一种动态渲染方式的一种而已,还有很多的页面渲染的方式。

有些时候Ajax接口也有很多的加密参数,让分析Ajax来爬取数据时也有很多的困难。

为了解决这些问题,可以直接模拟浏览器运行,再去爬取数据

这样就可以实现在浏览器中看到的是什么,爬到的就是什么,实现所见即所爬

如何获取加密的Ajax数据

在实战中Ajax的接口一般都是有加密参数的,如tokensign之类的,只有在使用正确的参数数据才能获得数据。

获得这些加密参数的方法有两种:

  • 分析逻辑,找到构造逻辑。(非常难!)
  • 直接以浏览器的方式运行,因为浏览器里是可以看到这个数据的。

Selenium这个工具就可以完成第二种操作。

Selenium简介

Selenium是一个自动化测试工具,使用它可以驱动浏览器完成特定的操作。

可以使用它来获取浏览器当前呈现页面的源代码。

对于用JavaScript动态渲染的页面来说,这种爬取方式非常有效。

准备工作

确保安装好了Chrome浏览器,配置好ChromeDriver,注意要保持版本一致,让后将ChromeDriver.exe移到Python中的Scripts目录

官方安装地址:Google Chrome 网络浏览器(最新版本可能没有对应的ChromeDriver
低版本Chrome镜像站
ChromeDriver下载地址

安装好Selenium

pip install selenium

更多的操作可以参照官方文档:Selenium with Python中文翻译文档 — Selenium-Python中文文档 2 documentation (selenium-python-zh.readthedocs.io)

基本用法

from selenium import webdriver
# 导入selenium库中的webdriver模块,用于驱动浏览器进行自动化操作

from selenium.webdriver.common.by import By
# 导入selenium库中的By模块,用于指定元素定位方式

from selenium.webdriver.common.keys import Keys
# 导入selenium库中的Keys模块,用于模拟键盘操作

from selenium.webdriver.support import expected_conditions as EC
# 导入selenium库中的EC模块,用于等待条件

from selenium.webdriver.support.wait import WebDriverWait
# 导入selenium库中的WebDriverWait模块,用于等待页面元素加载

browser = webdriver.Chrome()
# 创建一个Chrome浏览器的实例,用于自动化操作

try:
    browser.get('https://baidu.com')
    # 使用浏览器打开网页https://baidu.com

    # browser.find_element_by('kw') 方法已更新为以下方式
    input = browser.find_element(By.ID, 'kw')
    # 使用By.ID定位方式,找到id为'kw'的输入框元素,并将其赋值给input变量

    input.send_keys('Python')
    # 在输入框中输入关键字'Python'

    input.send_keys(Keys.ENTER)
    # 模拟键盘按下Enter键,提交搜索

    wait = WebDriverWait(browser, 10)
    # 设置一个最长等待时间为10秒的WebDriverWait对象,用于等待页面元素出现

    wait.until(EC.presence_of_element_located((By.ID, 'content_left')))
    # 等待直到'id'为'content_left'的元素出现在页面中,即等待搜索结果加载完毕

    print(browser.current_url)
    # 打印当前页面的URL

    print(browser.get_cookies())
    # 打印当前页面的所有cookie信息

    print(browser.page_source)
    # 打印当前页面的源代码

finally:
    browser.close()
    # 关闭浏览器窗口

这个基本实例演示了如何调用Chrome来浏览百度的页面并且搜索Python的操作,之后会跳转到搜索结果的页面。

selenium来操作Chrome浏览器来加载网页,可以直接拿到JavsScript渲染的结果。

Selenium的使用

初始化浏览器对象

Selenium支持的浏览器非常多

from selenium import webdriver

browser_Chrome = webdriver.Chrome()
browser_Safari = webdriver.Safari()
browser_Edge = webdriver.Edge()
browser_Firefox = webdriver.Firefox()

之后就会调用相应的浏览器

访问页面

browser_Chrome.get('https://www.taobao.com')
print(browser_Chrome.page_source)
browser_Chrome.close()

在初始化浏览器对象之后,使用get()方法可以驱动浏览器打开页面,并且可以通过.page_source来获取网页的源码。

查找节点

selenium也可以驱动浏览器完成各种操作,比如填充表单、模拟点击,但是进行操作之前就要先知道要操作的地方在哪,所以查找节点在哪就比较重要了。

单个节点

比如想要查找淘宝的搜索框,就要先分析页面的源代码了。

在淘宝网页打开开发者模式,查看搜索框元素:

可以发现搜索框的id属性是q,name属性也是q,还有其他的一些属性。

可以通过find_element(by,vulue)多种方式来获取发现它们:

  • 通过属性来获取
  • 通过XPath,``来获取
  • 通过CSS选择器来获取
from selenium.webdriver.common.by import By

browser_Chrome = webdriver.Chrome()

input_1 = browser_Chrome.find_element(by=By.ID, value='q')
input_2 = browser_Chrome.find_element(by=By.CSS_SELECTOR, value='#q')
input_3 = browser_Chrome.find_element(by=By.XPATH, value='//*[@id="q"]')
print(input_1, input_2, input_3)
<selenium.webdriver.remote.webelement.WebElement (session="24c49d20dc6e6892801487df51baf88c", element="423A8256F92CB6C6F85B6A9222EF1489_element_2")> 
 <selenium.webdriver.remote.webelement.WebElement (session="24c49d20dc6e6892801487df51baf88c", element="423A8256F92CB6C6F85B6A9222EF1489_element_2")> 
 <selenium.webdriver.remote.webelement.WebElement (session="24c49d20dc6e6892801487df51baf88c", element="423A8256F92CB6C6F85B6A9222EF1489_element_2")>

可以看到都获取了搜索框的对象

多个节点

使用方法find_elements(by,value)可以获取页面中的多个节点,放回是一个列表,对象的类型都是selenium.webdriver.remote.webelement.WebElement

input = browser_Chrome.find_elements(by=By.CSS_SELECTOR,value='.service-bd li')
print(input)

与节点进行交互

selenium同时也可以驱动浏览器与网页进行交互,比如send_keys()输入文字,用clear()来清空文字,click()来点击按钮。

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

url = 'https://taobao.com'
browser = webdriver.Chrome()
browser.get(url)
input = browser.find_element(by=By.ID, value='q')
input.send_keys('Huawei')
time.sleep(1)
input.clear()
input.send_keys('Logic')
button = browser.find_element(by=By.CLASS_NAME,value='btn-search')
button.click()

这段程序会打开淘宝的页面,之后会在搜索框中输入Huawei,一秒之后会清空后会输入Logic,然后会点击搜索按钮。

动作链

还有一些操作,它们没有特定的执行对象,比如鼠标拖拽、键盘按键等这些操作需要用到另一种方式来执行,那就是动作链(ActionChains)。

from selenium import webdriver
# 导入selenium库中的webdriver模块,用于驱动浏览器进行自动化操作

from selenium.webdriver.common.by import By
# 导入selenium库中的By模块,用于指定元素定位方式

from selenium.webdriver import ActionChains
# 导入selenium库中的ActionChains模块,用于模拟鼠标操作

option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)
# 创建ChromeOptions对象,用于设置浏览器选项
# add_experimental_option方法可以用于设置实验性选项,这里设置"detach"为True,表示不自动关闭浏览器窗口

browser = webdriver.Chrome(options=option)
# 创建一个Chrome浏览器的实例,并使用设置的选项进行配置

url = 'https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
# 设置目标网页的URL,这里是一个菜鸟网站的拖拽示例页面

browser.get(url)
# 使用浏览器打开指定的URL页面

browser.switch_to.frame('iframeResult')
# 切换到名为'iframeResult'的网页框架中,因为拖拽元素在该框架内

source = browser.find_element(by=By.CSS_SELECTOR, value='#draggable')
# 通过CSS选择器定位拖拽元素,返回一个WebElement对象,并将其赋值给source变量

target = browser.find_element(by=By.CSS_SELECTOR, value='#droppable')
# 通过CSS选择器定位目标元素,返回一个WebElement对象,并将其赋值给target变量

actions = ActionChains(browser)
# 创建ActionChains对象,用于模拟鼠标操作

actions.drag_and_drop(source, target)
# 使用drag_and_drop方法模拟鼠标拖拽操作,将source元素拖拽到target元素上

actions.perform()
# 执行ActionChains中的操作

运行JavaScript

还有一些操作selenium没有提供操作的API,比如下拉进度条,这种情况需要模拟运行JavaScript,用execute_script来实现。

from selenium import webdriver
from selenium.webdriver.common.by import By

option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)

browser = webdriver.Chrome(options=option)

browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')

这段代码会让浏览器打开知乎的页面,然后下拉到底部,之后弹出警告框。

获取节点的信息

通过page_source属性获取了页面的源代码之后,就可以使用解析库来解析网页的解析从中获取信息了。

  1. 获取属性

    使用get_attribute()方法获取节点的属性

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    option = webdriver.ChromeOptions()
    option.add_experimental_option("detach", True)
    
    browser = webdriver.Chrome(options=option)
    
    url = 'https://spa2.scrape.center/'
    browser.get(url)
    logo = browser.find_element(by=By.CLASS_NAME, value='logo-image')
    print(logo)
    print(logo.get_attribute('src'))
    
    <selenium.webdriver.remote.webelement.WebElement (session="c43dc64a285e943c6fc6b4dffe8cfd07", element="396816979FD7F3F7A89A9FC86D2FF265_element_3")>
    https://spa2.scrape.center/img/logo.a508a8f0.png
    
  2. 获取文本值

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    option = webdriver.ChromeOptions()
    option.add_experimental_option("detach", True)
    
    browser = webdriver.Chrome(options=option)
    
    url = 'https://spa2.scrape.center/'
    browser.get(url)
    
    input = browser.find_element(by=By.CLASS_NAME,value='logo-title')
    print(input.text)
    
    Scrape
    
  3. 获取ID、位置、标签名和大小

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    option = webdriver.ChromeOptions()
    option.add_experimental_option("detach", True)
    
    browser = webdriver.Chrome(options=option)
    
    url = 'https://spa2.scrape.center/'
    browser.get(url)
    input = browser.find_element(by=By.CLASS_NAME,value='logo-title')
    print(input.id)
    print(input.tag_name)
    print(input.size)
    
    
    F7DFF88A66646E84B4F59B183A5EE9F4_element_4
    span
    {'height': 40, 'width': 77}
    
    

切换Frame

网页中有一种节点叫做iframe

当涉及到在网页内嵌入其他网页或文档时,iframe(内嵌框架)是一种常用的HTML元素。它允许开发人员将一个网页嵌入到另一个网页中,创建一个内联的窗口。

以下是iframe的基本语法:

<iframe src="URL" width="宽度" height="高度"></iframe>
  • src:指定要嵌入的网页的URL。可以是外部网页,也可以是相对于当前网页的内部网页。
  • width:指定iframe的宽度,可以使用像素或百分比。
  • height:指定iframe的高度,同样可以使用像素或百分比。

通过设置适当的宽度和高度,你可以控制iframe的尺寸。这样,嵌入的网页将以预定义的大小显示在父网页中。

selenium默认打开的是父Frame,如果这个页面有子页面的话,用switch_to.frame()方法可以切换页面。

import time
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

option = webdriver.ChromeOptions()
option.add_experimental_option('detach', True)
url = 'https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'

browser = webdriver.Chrome(options=option)
browser.get(url)
browser.switch_to.frame('iframeResult')
try:
    logo = browser.find_element(by=By.CLASS_NAME, value='logo')
except NoSuchElementException:
    print('No Logo')
browser.switch_to.parent_frame()
logo = browser.find_element(by=By.CLASS_NAME, value='logo')
print(logo)
print(logo.text)

No Logo
<selenium.webdriver.remote.webelement.WebElement (session="27aca41206b95bac361def8e033ad660", element="578B75883B58A4C5F1CE79366B3D5500_element_11")>

等待

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


# 隐式等待
def implicitly_wait():
    browser = webdriver.Chrome()
    browser.implicitly_wait(10)
    browser.get('https://spa2.scrape.center/')
    input = browser.find_element(by=By.CLASS_NAME, value='logo-image')
    print(input)


# 显示等待
def wait():
    browser = webdriver.Chrome()
    browser.get('https://taobao.com/')
    wait = WebDriverWait(browser, 10)
    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)

隐式等待是执行测试时如果没有在DOM中找到节点,将会继续等待,超出设定的时间后会抛出找不到节点的异常。

但是隐式等待只规定了规定的时间,但是页面的加载时间也会受往网络因素的限制。

所以显示等待会指定要查找的节点和最长的等待时间。如果在规定的时间内加载出了要查找的节点,就返回这个节点,如果超出了时间还未加载出来,就会抛出超时异常。

显示等待首先引入了WebDriverWait对象,指定最长等待时间为10s,并赋值给wait变量。

until传入等待条件。

presence_of_element_located()代表节点出现,其参数时一个元组(By.ID, 'q'),表示节点id为p。这样做的效果是如果节点ID为q的节点在1-s内加载出来了,就返回节点,超出时间就会,抛出异常。

element_to_be_clickale代表按钮可点击,所以查找按钮要查找CSS选择器.btn-search的按钮,如果10s内它是可点击的,也就是这个按钮加载出来了,就返回该节点,超出10s就会返回异常。

更多尝试参照官方文档。

前进与后退

forward()也是使页面前进,back()会使页面回退。

from selenium import webdriver
import time
browser = webdriver.Chrome()

browser.get('https://www.baidu.com')
browser.get('https://www.zhihu.com')
browser.get('https://www.bilibili.com')

browser.back()
time.sleep(2)
browser.back()
time.sleep(2)
browser.forward()

Cookies

Selenium可以对Cookies继续添加、获取、删除。

from selenium import webdriver

browser = webdriver.Chrome()

browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
[{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'fb3eda1aa35a9ed9f88f346a7a3ebe83|1690034393|1690034391'}, {'domain': '.zhihu.com', 'expiry': 1721570392, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'AFAXDT6NHxePTlrdiFNdX5JOaIROCTM5IAE=|1690034391'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '8f3aad49-42e8-49c6-855b-20baf9f6856b'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': '_zap', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '6aa41b50-3148-40c4-9a21-8ba28590f314'}]
[{'domain': '.www.zhihu.com', 'httpOnly': False, 'name': 'name', 'path': '/', 'sameSite': 'Lax', 'secure': True, 'value': 'germey'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'fb3eda1aa35a9ed9f88f346a7a3ebe83|1690034393|1690034391'}, {'domain': '.zhihu.com', 'expiry': 1721570392, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'AFAXDT6NHxePTlrdiFNdX5JOaIROCTM5IAE=|1690034391'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'SESSIONID', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'OuJsIxv19DcvQyFBu8oHvvjV0sNLyaT52BcsPHuIa2d'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '8f3aad49-42e8-49c6-855b-20baf9f6856b'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': '_zap', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '6aa41b50-3148-40c4-9a21-8ba28590f314'}]
[]

选项卡管理

Selenium也可以对选项卡进行操作。

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.execute_script('window.open()') # 新开页面
print(browser.window_handles) # 页面的多个选项卡是列表
browser.switch_to.window(browser.window_handles[1]) # switch_to转化到新页面
browser.get('https://www.taobao.com/')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://www.python.org/')
['C052838A03EEFCB58CF1B6163CFCD135', '9080FFD32E5016C380402E89E8B2597C']

这段代码会先打开百度的页面,之后会打开一个新的选项卡,然后打开淘宝,之后回到百度的页面,转到python官网。

异常

参照官方文档

反屏蔽

有些网站会对Selenium进行屏蔽。

检测的基本原理是接触当前浏览器窗口下的window.navigator对象中是否包含webdriver的属性,正常使用浏览器的时候属性undefined,一旦使用了Selenium后就会变成webdriver属性.

Selenium中,可以使用CDP(Chrome Devtools Protocol 开发工具协议)解决这个问题,利用它可以将每次页面刚加载的时候就会执行JavaScript语句,将webdriver的属性置空。

这里的执行的方法是Page.addScriptToEvaluateOnNewDocument,将语句传入其中即可。

还可以加入几个选项来隐藏WebDriver提示条和自动化扩展信息

from selenium import webdriver
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_experimental_option('useAutomationExtension', False)
browser = webdriver.Chrome(options=option)
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
    'source': 'Object.defineProperty(navigator,"webdriver",{get:() => undefined})'
})
browser.get('https://antispider1.scrape.center/')

无头模式

无头模式下,在网站运行的时候就不会弹出窗口,从而减少干扰,同时减少资源的占用。

from selenium import webdriver
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_argument('--headless')
browser = webdriver.Chrome(options=option)
browser.set_window_size(1366,768)
browser.get('https://www.baidu.com/')
browser.get_screenshot_as_file('preview.png') # 截取当前页面

无头模式下,页面就不会弹出来了,代码会继续运行,

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Scrapy可以使用Selenium或Splash来爬取动态加载页面。其中,Selenium是一个自动化测试工具,也可以模拟用户在浏览器中的操作,从而获取页面内容。而Splash是一个JavaScript渲染服务,可以模拟浏览器渲染页面,并且提供了API接口,方便与Scrapy集成。下面分别介绍使用Selenium和Splash来爬取动态加载页面的方法。 1. 使用Selenium 首先需要安装selenium库和对应的浏览器驱动,如ChromeDriver。然后在Scrapy的Spider中使用Selenium来获取页面内容,示例代码如下: ``` from scrapy import Spider from selenium import webdriver class MySpider(Spider): name = 'myspider' def start_requests(self): url = 'http://example.com' yield webdriver.Request(url, self.parse) def parse(self, response): driver = response.request.meta['driver'] # 使用driver来操作页面,获取需要的内容 ... ``` 在start_requests方法中,使用webdriver.Request来发送请求,并将回调函数设置为parse。在parse方法中,通过response.request.meta['driver']获取到了Selenium的WebDriver对象,然后就可以使用该对象来获取页面内容。 2. 使用Splash 首先需要安装Splash服务,并启动该服务。然后在Scrapy的Spider中使用Splash来获取页面内容,示例代码如下: ``` import scrapy from scrapy_splash import SplashRequest class MySpider(scrapy.Spider): name = 'myspider' start_urls = ['http://example.com'] def start_requests(self): for url in self.start_urls: yield SplashRequest(url, self.parse, args={'wait': 0.5}) def parse(self, response): # 使用response来操作页面,获取需要的内容 ... ``` 在start_requests方法中,使用SplashRequest来发送请求,并将回调函数设置为parse。在args参数中,可以设置Splash的一些参数,如wait表示等待时间。在parse方法中,直接使用response来获取页面内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值