已写章节
第一章 网络爬虫入门
第二章 基本库的使用
第三章 解析库的使用
第四章 数据存储
第五章 动态网页的抓取
文章目录
第五章 动态网页的抓取(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浏览器。
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的例子
感谢你的阅读!