动态网页的抓取


已写章节

第一章 网络爬虫入门
第二章 基本库的使用
第三章 解析库的使用
第四章 数据存储
第五章 动态网页的抓取

文章目录


第五章 动态网页的抓取(Selenium)

有时候我们在用requests抓取页面的时候,得到的结果可能和我们在浏览器中看到的结果不一样:在浏览器中可以正常看到页面数据,但是使用requests得到的结果并没有。这是因为requests库获取的都是原始的HTML文档,而在浏览器中看到的页面是经过JavaScript处理数据后生成的结果,这些数据来源有很多种,可能是通过Ajax加载的,可能是包含在HTML文档中的,也有可能是经过JavaScript和特定的算法计算后生成的。如果是通过Ajax加载的,原始页面最初不会包含某些数据,原始页面加载完之后,会再向服务器请求某个接口获取数据,然后数据才被处理从而呈现在网页上,这其实就是发生了一个Ajax请求。如果遇到这样的页面,需要分析出网页后台向服务器发送的Ajax请求,如果可以使用requests来模拟Ajax请求,那么就可以成功抓取了。


5.1 Ajax的原理

Ajax(Asynchronous JavaScript nad XML),即异步的JavaScript和XML。它不是一门编程语言,而是利用JavaScript在保证网页不被刷新、页面不改变的情况下与服务器交换数据并更新部分网页的技术。

例如:打开tx视频,在电影中,鼠标的滑轮向下划,你会发现电影好像没有尽头,有时候最下面会出现加载的动画。页面其实并没有整个刷新,链接也并没有变化,但是网页中新增加了电影,这就是通过Ajax获取新数据并呈现的过程。

Ajax有其特殊的请求类型,它是xhr,可以在Chrome的检查中选择xhr查看。

模拟Ajax请求来爬取tx视频的例子:

import requests
from fake_useragent import UserAgent
from lxml import etree
import re
from typing import NoReturn, List
import time
import mysql.connector

def start() -> NoReturn:
    first_url = 'https://v.qq.com/channel/movie?listpage=1&channel=movie&itype=100062'
    base_url = 'https://v.qq.com/x/bu/pagesheet/list?append=1&channel=movie&itype=100062&listpage=2&offset={}&pagesize=30'
    create_table()
    storage_MysqlDB(get_page(first_url), 1)
    page_numbers = ((get_number_of_movies(first_url)-30)//30)+1
    for i in range(1, page_numbers):
        url = base_url.format(str(i*30))
        storage_MysqlDB(get_page(url), (i*30)+1)
        # time.sleep(5)


def get_number_of_movies(url: str) -> int:
    headers = {
        'user-Agent': UserAgent().chrome
    }
    response = requests.get(url=url, headers=headers)
    response.encoding = response.apparent_encoding
    e = etree.HTML(response.text, etree.HTMLParser())
    number_of_movies = e.xpath('//body/div[5]/div/div/div/span/text()')[0]
    return int(number_of_movies)

def get_page(url: str) -> List[List]:
    response = requests.get(url=url, headers={'user-Agent': UserAgent().chrome})
    response.encoding = response.apparent_encoding
    e = etree.HTML(response.text, etree.HTMLParser())

    all_div = e.xpath('//div[@class="list_item"]')
    res = []
    for div in all_div:

        movies_name = div.xpath('./div[1]/a/@title')

        hrefs = div.xpath('./div[1]/a/@href')

        performers = div.xpath('./div[1]/div/text()')
        performers = [i.replace('主演:', '') for i in performers]

        number_of_observers = div.xpath('./div[2]/text()')

        film_length = div.xpath('./a/div[1]/text()')

        score = div.xpath('./a/div[2]/text()')



        if len(movies_name) == 0:
            movies_name = ['None']

        if len(hrefs) == 0:
            hrefs = ['None']

        if len(performers) == 0:
            performers = ['None']

        if len(number_of_observers) == 0:
            number_of_observers = ['None']

        if len(film_length) == 0:
            film_length = ['None']

        if len(score) == 0:
            score = ['None']

        tem = []
        tem.append(str(movies_name[0]))
        tem.append(str(hrefs[0]))
        tem.append(str(performers[0]))
        tem.append(str(number_of_observers[0]))
        tem.append(str(film_length[0]))
        tem.append(str(score[0]))
        res.append(tem)
    print(res)
    return res


def create_table():
    mydb = mysql.connector.connect(
        host='localhost',
        user='root',
        passwd='123456',
        port=3307,
        charset='utf8'
    )
    mycursor = mydb.cursor()
    sql_create_table = [
        "USE reptile;",
        "CREATE TABLE IF NOT EXISTS tencent_movies(\
         ID INT AUTO_INCREMENT COMMENT '编号',\
         movies_name VARCHAR(40) COMMENT '电影名',\
         hrefs VARCHAR(100) COMMENT '链接',\
         performers VARCHAR(100) COMMENT '主演',\
         number_of_observers VARCHAR(20) COMMENT '播放量',\
         film_length VARCHAR(20) COMMENT '电影时长',\
         score varchar(4) COMMENT '评分',\
         PRIMARY KEY(ID)\
         )COMMENT = 'All movie information in Tencent video'\
         ENGINE = INNODB CHARSET='utf8mb4' COLLATE='utf8mb4_unicode_ci';"
    ]
    for i in sql_create_table:
        mycursor.execute(i)

def storage_MysqlDB(data: List, start: int)-> None:
    mydb = mysql.connector.connect(
        host='localhost',
        user='root',
        passwd='123456',
        port=3307,
        charset='utf8',
        database='reptile'
    )
    mycursor = mydb.cursor()
    sql_insert = "insert into tencent_movies values(%s, %s, %s, %s, %s, %s, %s)"
    for i in data:
        i.insert(0, start)
        new_i = tuple(i)
        mycursor.execute(sql_insert, new_i)
        # print(start)
        # time.sleep(1)
        start += 1
    mydb.commit()
    mycursor.close()

if __name__ == '__main__':
    start()

在上面,我们介绍了Ajax,并使用requests来模拟Ajax请求来爬取数据。

但是,JavaScript动态渲染页面不止Ajax这一种。有很多网页是由JavaScript产生的,并不会包含Ajax请求。淘宝页面也是使用Ajax来获取数据的,但是,他的Ajax接口中包含许多的加密参数,我们很难找出其规律,也很难使用requests来模拟Ajax请求。

为了解决这些问题,我们可以直接使用模拟浏览器的方式来实现,这样就可以做到在浏览器中看到什么样子,抓取的源码就是什么样子。


5.2 Selenium的使用

Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如下拉、点击按钮、在文本框中输入文字、下划页面等动作,同时还可以获取浏览器当前呈现的页面的源代码。可以将Selenium看做是个通过代码来控制浏览器的工具。


5.2.1 准备工作

  • 需要下载与你的Chrome版本相对应的ChromeDriver
  • 安装Selenium库
  • 将下载好的浏览器驱动放在Python的安装目录下的Scripy目录中

注意:使用Edge的驱动器会提示要将驱动添加到path中,可以在初始化一个Edge浏览器对象的时候传入Edge驱动的路径:

from selenium import webdriver

browser = webdriver.Edge('E:\python\Scripts\msedgedriver.exe') 
browser.get('https://www.baidu.com')

所以建议大家使用Chrome浏览器。

ChromeDriver下载地址

Edge驱动下载地址

Selenium官方文档


5.2.2 Selenium的使用

当所有的准备工作都完成后,打开编辑器,来敲代码了。

首先来看看下面这个小例子:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

browser = webdriver.Chrome()  # 声明一个chrome浏览器对象
try:
    browser.get('https://www.baidu.com')   # 打开指定的网页
    input = browser.find_element_by_id('kw')   # 寻找搜索框
    input.send_keys('Python')   # 在搜索框中输入指定的内容
    input.send_keys(Keys.ENTER)
    wait = WebDriverWait(browser, 10)
    wait.until(EC.presence_of_all_elements_located((By.ID, 'content_left')))
    print(browser.current_url)  # 打印当前页面的url
    print(browser.get_cookies()) # 打印当前页面的cookies
    print(browser.page_source)  # 打印当前页面的源代码
finally:
    browser.close()

执行上面的代码,如果一切正常的话,你的电脑应该自动打开了Chrome浏览器并来到了百度的首页,然后自动在搜索框中输入了“Python”并点击了搜索按钮,控制台中打印出当前页面的url、cookie、页面源代码。


1. 声明浏览器对象

Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端的浏览器。

下面是初始浏览器的方法:

from selenium import webdriver

browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()

注意:
可以指定驱动器路径来初始化浏览器对象:

browser = webdriver.Edge(‘E:\python\Scripts\msedgedriver.exe’)


2. 访问页面

可以使用get()方法来请求网页,将要请求的url传入即可,下面是请求百度的例子:

from selenium import webdriver
import time

browser = webdriver.Edge('E:\python\Scripts\msedgedriver.exe')
browser.get('https://www.baidu.com')
print(browser.page_source)
time.sleep(10)
browser.close()

3. 查找节点

Selenium可以驱动浏览器完成各种操作,比如填充表单、模拟点击等。比如,我们想要完成在某个输入框输入文本的操作,我们首先需要使用selenium查找到这个输入框所在的节点位置。


  • 查找单个节点的常用方法

    查找单个节点的方法
    find_element_by_id() 通过节点的id属性值来查找单个节点
    find_element_by_name() 通过节点的name属性值来查找单个节点
    find_element_by_xpath() 通过xpath来查找单个节点
    find_element_by_link_text() 通过完整的链接文本(点击该文本就会跳到另一个页面)查找单个节点
    find_element_by_partial_link_text() 通过部分链接文本来查找单个节点
    find_element_by_tag_name() 通过节点的标签名来查找单个节点
    find_element_by_class_name() 通过节点的class属性值来查找单个节点
    find_element_by_css_selector() 通过css来查找当个节点
    find_element(By.ID,‘ ’) 同find_element_by_id()
    find_element(By.CLASS_NAME, ‘ ’) 同find_element_by_class_name()
    find_element(By.CSS_SELECTOR,’ ’) 同find_element_by_css_selector()
    find_element(By.LINK_TEXT,’ ’) 同find_element_by_link_text()
    find_element(By.NAME,’ ’) 同find_element_by_name()
    find_element(By.PARTIAL_LINK_TEXT,’ ’) 同find_element_by_partial_link_text()
    find_element(By.TAG_NAME,’ ’) 同find_element_by_tag_name()
    find_element(By.XPATH,’ ’) 同find_element_by_xpath()

例子:

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

browser = webdriver.Chrome()
url = 'https://www.taobao.com/'
browser.get(url)
print(browser.find_element(By.ID, 'q'))
print(browser.find_element_by_id('q'))
print(browser.find_element_by_xpath('//*[id="q"]'))
print(broswer.find_element_by_css_selector('#q'))

运行结果:

<selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")>
<selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")>
<selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")>
<selenium.webdriver.remote.webelement.WebElement (session="5e8df8d577c9340c9524ecebcf924800", element="18f8aa32-46d6-43cd-89b1-67e48e314310")>

4. 查找多个节点

在网页中如果查找的节点有多个,使用find_element()方法将只会找到第一个节点,如果想要查找多个节点,就需要使用find_elements()方法。

from selenium import webdriver

with webdriver.Chrome() as browser:
    browser.get('https://www.taobao.com')
    all_nodes = browser.find_elements_by_xpath('//li[@class="J_Cat a-all"]')
    print(len(all_nodes))
    print(all_nodes)

上面的代码将自动使用Chrome打开淘宝的主页,打印出查找到的淘宝主页左上角的15个导航栏。

将上面的表格中查找单个节点的方法中的element改为elements就是查找多个节点的方法。


5. 节点交互

Selenium可以驱动浏览器来执行一些操作,也就是可以让浏览器执行一些动作。

常用的有:输入文字时用send_keys()方法,清空文字时用clear()方法,点击时用click()方法。

from selenium import webdriver
import time

with webdriver.Chrome() as browser:
    browser.get('https://www.taobao.com')  # 打开链接
    input = browser.find_element_by_xpath('//*[@id="q"]')  # 找到搜索框
    input.send_keys('背包')  # 在搜索框中输入背包
    time.sleep(5)
    input.clear()  # 清空搜索框
    input.send_keys('书包')  # 在搜索框中输入书包
    search_button = browser.find_element_by_xpath('//button[@class="btn-search tb-bg"]')  # 查找搜索按钮
    time.sleep(5)
    search_button.click()  # 点击搜索按钮

6. 动作链

上面的选择输入框、向输入框中输入文本、点击按钮等动作,都是有特定的执行对象;但是,对于鼠标移动、按盘按键等,这些动作由另一种方式来执行,这就是动作链。

from selenium import webdriver
from selenium.webdriver import ActionChains
import time

browser = webdriver.Chrome()
browser.get('示例网站的url')
browser.find_element_by_xpath('//input[@placeholder="请输入邮箱"]').send_keys('jxd')
time.sleep(1)
browser.find_element_by_xpath('//input[@placeholder="请输入密码"]').send_keys('123456')
time.sleep(1)
browser.find_element_by_xpath('//div[@class="geetest_wait"]').click()
time.sleep(1)

# 更换监视界面
ifame = browser.find_element_by_xpath('//div[@class="inner-conntent"]')
browser.switch_to.frame(ifame)
# 要拖动的元素
source = browser.find_element_by_xpath('//div[@class="geetest_slider_button"]')
# 要拖动到的位置元素
target = browser.find_element_by_xpath('//div[@class="geetest_slider_tip geetest_fade"]')
actions = ActionChains(browser)
actions.drag_and_drop(source, target)
actions.perform()

7. 滑动页面

滑动页面其实就是执行js语句,可以将控制页面滑动的js语句放在driver.execute_script(js语句)中来执行:

driver.execute_script('window.scrollBy(0,1000)')  # 滑动的距离,分为x轴和y轴

driver.execute_script('window.scrollTo(0,1000)')  # 滑动到指定的坐标

driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")  # 滑动到页面的最下方

driver.execute_script("document.getElementById('page').scrollIntoView(true)") # 将查找到的元素显示在屏幕中间,scrollIntoView(false)就是将查找到的元素显示在屏幕的底部 

8. 执行JavaScript

上面的向下滑动页面就是执行JavaScript的例子,我们可以使用driver.execute_script(javascript语句)来执行JavaScript代码。


9. 获取节点的信息

方法 作用
WebElement.get_attribute(‘属性名’) 获取标签指定的属性的属性值
WebElement.text 获取标签的文本
WebElement.id 获取标签的id
WebElement.location 获取标签在页面中的相对位置
WebElement.tag_name 获取标签的标签名
WebElement.size 获取标签的大小

10. 切换Frame

网页中有一种节点叫做iframe,也就是子页面,我们使用selenium提供的选择节点的方法是在父级Frame里面操作,而如果页面中有子页面,它是不能获取到子Frame中的节点的。此时,就需要使用switch_to.frame()方法来切换Frame,上面的例子中有。

注意:switch_to.frame()默认接收的是目标frame节点的id或name,如果目标节点没有id属性和name属性,那么就可以传入一个用select_element_by_xpath()等方法定位到的WebElement对象。


11. 等待

有时我们请求的页面中的元素或节点是通过Ajax加载的,就需要一定的时间,在元素未加载出来之前我们去查找该元素就会找不到,所以,就需要等待一段时间,等元素加载出来之后再执行查找元素等一系列操作。


隐式等待:在查找节点而节点并没有立即出现时,隐式等待会等待一段时间再查找节点,时间默认是0秒,如果还未找到节点就会抛出节点未找到的异常。隐式等待可以看做是time.sleep()方法。

browser.imlicitly_wait(10)  # 隐式等待10秒钟

显示等待:规定一个最长等待时间,在这段时间里如果查找到了节点就返回节点,如果在这段时间里还找到该节点,则抛出超时异常。

from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(browser, 10)  # 设置显式等待时间是10秒
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))

until()方法接收一个等待条件expected_conditions,上面的例子中的等待条件是presence_of_element_located(),指的是节点出现的意思,其参数是节点的定位元组,也就是ID为q的节点。


常见的等待条件

等待条件 含义
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 某个节点值包含某个文字
frame_to_be_avaliable_and_switch_to_it 加载并切换
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 是否出现警告

12. 控制页面前进和后退

from seleniun import webdriver

browser.forward()    # 前进
browser.back()   # 后退

13. 对cookies的操作

from selenium import webdirver

browser.get_cookies()  # 返回字典类型的所有cookies
browser.add_cookies()  # 添加一个cookies,参数是字典类型
browser.delete_all_cookies()   # 删除所有的cookies

14. 选项卡

from selenium import webdriver
import time

browser = webdriver.Chrome()  # 初始化一个浏览器对象

browser.get('https://www.baidu.com')  # 访问百度
browser.execute_script('window.open()')   # 新开启一个选项卡
time.sleep(2)

print(browser.window_handles)  # 打印所有选项卡代号列表

browser.switch_to.window(browser.window_handles[1])   # 更换到选项卡代号列表中的第一个选项卡
time.sleep(1)

browser.get('https://www.taobao.com')
browser.switch_to.window(browser.window_handles[0])
time.sleep(1)

browser.get('https://python.org')

15. 模拟输入键盘

上面讲的sendkeys()方法可以向输入框中输入文字,它还可以实现模拟键盘的输入,甚至是组合键。

from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get(“[https://www.baidu.com](https://www.baidu.com/)”)

el = driver.find_element(By.ID, "kw") # 找到输入框
el.send_keys(“seleniumm”)             # 在输入框中输入"seleniumm"
el.send_keys(Keys.BACK_SPACE)         # 模拟按一下Backspaece键
el.send_keys(Keys.SPACE)              # 模拟按一下Space键
el.send_keys(“教程”)                  # 输入"教程"
el.send_keys(Keys.CONTROL, ‘a’)       # 模拟按下 "a"键
el.send_keys(Keys.CONTROL, ‘x’)       # 模拟按下 "x"键
el.send_keys(Keys.CONTROL, ‘v’)       # 模拟按下 "v"键
el.send_keys(Keys.ENTER)              # 模拟按下"enter"键

16. 异常处理

selenium的异常类型都在selenium.common.exceptions这个包中,下面列举了常用的异常:

异常 说明
NoSuchElementException 未找到节点
NoSuchAttributeException 节点没有改属性
NoSuchFrameException frame未找到
NoSuchWindowException window未找到

知道这些常用的异常之后,就可以使用try except来将这些异常捕获并做相应的处理了。


5.3 使用Selenium的例子

使用Selenium爬取手机信息


感谢你的阅读!

上一篇:常见反爬机制及其破解方法


下一篇:python+selenium实现网站图片批量下载保存