为了解决异步渲染网页,我们直接模拟浏览器运行的方式来实现,这样就可以左到在浏览器中看到什么样,抓取的源码就是什么样,也就是可见即可爬。这样我们就可以不用管网页内部的javascript用了什么算法渲染页面,也就是所谓的js加密,也不用管网页后台的Ajax接口有哪些参数。
1Selenium的使用
在使用selenium之前,需要安装Driver文件,有ChromeDriver(适用chrome),GeckoDriver(适用firefox),PhantomJS。如何下载到csdn上直接搜索就有,需要注意的是除了PhantomJS以外,另外两个需要和你本机安装的浏览器版本相近(不要求完全一样,但是必须是相近的版本),这是必须要注意的。下载完成后,把exe文件复制粘贴到你安装的python文件中bin文件(或者DOC)同级目录下。
1.1selenium的基本用法
from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
time.sleep(5)
driver.close()
driver.quit()
如果运行上面的代码之后,会弹出一个chrome浏览器页面,然后过5秒后退出 ,就说明你的运行文件版本以及安装位置是正确的,当然,这里可以换成Firefox().
那么我们还可以做一些什么事呢?
#coding=utf-8
from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
driver.find_element_by_id("kw").send_keys("长城")
driver.find_element_by_id("su").click()
time.sleep(5)
driver.quit()
我们这里根据id定位元素,定位一个位置并给予它一个参数,然后下面使用click()方法实行点击一个按钮。实际上这里是给我们的百度输入框中输入长城这个值,然后点击“百度”按钮。最后过5秒钟,关闭页面。
那么继续
from selenium import webdriver
import time
driver = webdriver.Chrome()
start_url = "https://www.baidu.com"
driver.get(start_url)
driver.find_element_by_id("kw").send_keys("长城")
driver.find_element_by_id("su").click()
time.sleep(5)
print(driver.page_source.encode('GBK','ignore').decode('GBK'))
driver.quit()
这里使用了编码解码的操作,为什么要这么做,是因为网页本身是utf-8,但我们爬取网页时用的是Unicode,但使用print()打印时,里面的内容应该是GBK编码,那么就需要我们将需要打印的数据后加上 .encode(‘GBK’,‘ignore’).decode(‘GBk’)第一个GBK是忽略掉非法字符,然后再译码。不然会报编码错误。这里会打印出网页的源码。
小结:
- selenium的导包:
from selenium import webdriver
- selenium创建driver对象:
webdriver.PhantomJS()
- selenium请求数据:
driver.get("http://www.baidu.com/")
- selenium查看数据:
driver.page_source
- 关闭无界面浏览器:
driver.quit()
- 根据id定位元素:
driver.find_element_by_id(“kw”)
- 操作点击事件:
click()
- 给输入框赋值:
send_keys()
1.2selenium元素定位的方法
1.2.1定位方法
find_element_by_id (返回一个元素)
find_elements_by_xpath (返回一个包含元素的列表)
find_elements_by_link_text (根据连接文本获取元素列表)
find_elements_by_partial_link_text (根据链接包含的文本获取元素列表)
find_elements_by_tag_name (根据标签名获取元素列表)
find_elements_by_class_name (根据类名获取元素列表)
注意: find_element
和find_elements
的区别 by_link_text
和by_partial_link_tex
的区别:全部文本和包含某个文本
这些解析方法在之前都有介绍,这里就不一一赘述,我们用一个例子来介绍这些方法。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.douban.com/")
ret1 = driver.find_element_by_id("anony-nav")
print(ret1)
# 输出为:<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>
ret2 = driver.find_elements_by_id("anony-nav")
print(ret2)
#输出为:[<selenium.webdriver.remote.webelement.WebElement (session="ea6f94544ac3a56585b2638d352e97f3", element="0.5335773935305805-1")>]
ret3 = driver.find_elements_by_xpath("//*[@id='anony-nav']/h1/a")
print(len(ret3))
#输出为:1
ret4 = driver.find_elements_by_tag_name("h1")
print(len(ret4))
#输出为:1
ret5 = driver.find_elements_by_link_text("下载豆瓣 App")
print(len(ret5))
#输出为:1
ret6 = driver.find_elements_by_partial_link_text("豆瓣")
print(len(ret6))
#输出为:20
driver.close()
这里调用的方法并不复杂,只是需要去网页中找到你想要的元素的id,或者class,或者xpath方法,需要注意的是xpath路径是可以直接在网页中右键点击elements中的元素,copy–》xpath,来获得。
还有css元素定位
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_class_name('s_ipt').send_keys('3浪还有')
# driver.find_element_by_css_selector('#su').click()
driver.find_element_by_class_name('btn self-btn bg s_btn').click()
#其他css方法
# 通过id属性(css属性)
driver.find_element_by_css_selector("#kw").send_keys("python")
# 通过class属性定位(css属性)
driver.find_element_by_css_selector(".s_ipt").send_keys("python")
# 通过标签定位(css属性)
driver.find_element_by_css_selector("input").send_keys("python")
# 通过name属性定位(其他属性)
driver.find_element_by_css_selector("[name='wd']").send_keys("python")
# 通过autocomplete属性定位(其他属性)
driver.find_element_by_css_selector("[autocomplete='off']").send_keys("python")
# 通过type属性定位(其他属性)
driver.find_element_by_css_selector("[type='text']").send_keys("python")
# css也可以通过标签与属性的组合来定位元素
driver.find_element_by_css_selector("input.s_ipt").send_keys("python")
driver.find_element_by_css_selector("input#kw").send_keys("python")
driver.find_element_by_css_selector("input[id='kw']").send_keys("python")
# css层级关系
driver.find_element_by_css_selector("form#form>span>input").send_keys("python")
driver.find_element_by_css_selector("form.fm>span>inout").send_keys("python")
# css逻辑运算
driver.find_element_by_css_selector("input[id='kw'][name='wd']").send_keys("python")
那么我们获得,或者说定位到了元素后我们需要怎么办呢?
1.2.2解析手段
在第一章介绍了赋予参数方法,和点击方法。那么还有什么方法?
find_element仅仅能够获取元素,不能够直接获取其中的数据,如果需要获取数据需要使用以下方法:
- 获取文本:
element.text
- 获取属性值:
element.get_attribute("href")
示例:
#coding=gbk
from selenium import webdriver
driver =webdriver.Chrome()
driver.get("https://www.douban.com/")
ret4 = driver.find_elements_by_tag_name("h1")
print(ret4[0].text)
#输出:豆瓣
ret5 = driver.find_elements_by_link_text("下载豆瓣 App")
print(ret5[0].get_attribute("href"))
#输出:https://www.douban.com/doubanapp/app?channel=nimingye
driver.close()
这里给出了返回txt内容和href,是不是和上一章的解析方法有些类似。
小结
- 根据xpath定位元素:
driver.find_elements_by_xpath("//*[@id='s']/h1/a")
- 根据class定位元素:
driver.find_elements_by_class_name("box")
- 根据link_text定位元素:
driver.find_elements_by_link_text("下载豆瓣 App")
- 根据tag_name定位元素:
driver.find_elements_by_tag_name("h1")
- 获取文本内容:
element.text
- 获取标签属性:
element.get_attribute("href")
1.2.3其他方法
selenium 处理cookie
通过driver.get_cookies()
能够获取所有的cookie
# 把cookie转化为字典
{cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()}
#删除一条cookie
driver.delete_cookie("CookieName")
# 删除所有的cookie
driver.delete_all_cookies()
页面等待
-
为什么需要等待
如果网站采用了动态html技术,那么页面上的部分元素出现时间便不能确定,这个时候就可以设置一个等待时间,强制要求在时间内出现,否则报错
-
页面等待的方法
time.sleep(10)
switch方法切换的操作
一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下:
也可以使用 window_handles 方法来获取每个窗口的操作对象。例如:
# 1. 获取当前所有的窗口
current_windows = driver.window_handles
# 2. 根据窗口索引进行切换
driver.switch_to.window(current_windows[1])
iframe是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是
driver.switch_to.frame()
在使用selenium登录qq邮箱的过程中,我们会发现,无法在邮箱的登录input标签中输入内容,通过观察源码可以发现,form表单在一个frame中,所以需要切换到frame中。这个方法使用的很多。
当你触发了某个事件之后,页面出现了弹窗提示,处理这个提示或者获取提示信息方法如下:
alert = driver.switch_to_alert()
页面前进和后退
driver.forward() #前进
driver.back() # 后退
selenium的优缺点
- selenium能够执行页面上的js,对于js渲染的数据和模拟登陆处理起来非常容易
- selenium由于在获取页面的过程中会发送很多请求,所以效率非常低,所以在很多时候需要酌情使用
小结
- 获取cookie:
get_cookies()
- 删除cookie:
delete_all_cookies()
- 切换窗口:
switch_to.window()
- 切换iframe:
switch_to.frame()
1.4实例说明
在给出例子之前,我们要想到,动态解析页面的时候,我们需要获取在网页上所有的内容,就必须要渲染出网页全部的页面,不然我们可能就会定位不到我们想要的信息,所以我们就需要进行页面的滚动,一直到页面最底部。
方法为:
from selenium import webdriver
import time, random
# 1.创建driver对象
driver = webdriver.Chrome()
# 访问url地址
driver.get('https://www.csdn.net')
for i in range(1, 10):
# 构造滑动函数,scrollTo滚动,参数为滑动的距离
js = 'scrollTo(0, {})'.format(1000 * i)
# execute_script 执行滑动轨迹
driver.execute_script(js)
time.sleep(random.randint(1, 3))
print(driver.page_source)
这样运行后,页面就会滑到最底部,无需记忆,直接复制粘贴就行。
我们来一个比较狠的,我们来做一个斗鱼的弹幕机。
from selenium import webdriver
from lxml import etree
import time
import re
class DySpider(object):
def __init__(self):
self.driver = webdriver.Chrome()
# 最大化窗口
self.driver.maximize_window()
self.start_url = 'https://www.douyu.com/directory/all'
self.driver.get('https://passport.douyu.com/member/login?')
time.sleep(10)
def parse_url(self):
"""
关闭弹窗,执行滑动
:return:html_element(elements内容), num(最大页码数)
"""
self.driver.get(self.start_url)
try:
"""访问后,没有弹窗,在此使用try嵌套"""
time.sleep(3)
self.driver.find_element_by_class_name('ZoomTip-tipHide').click()
except:
"""没有弹窗,则pass"""
pass
"""获取elements内容"""
time.sleep(2)
self.page_url()
html_element = self.driver.page_source
print(html_element)
"""提取翻页的最大页码数"""
num = re.findall('dy-Pagination-item dy-Pagination-item-(\d+)',html_element)[5]
"""验证是否正常(成功)获取"""
print('返回的页面共有:' + num)
return num
def page_url(self, num=None):
"""
构造向下滑动
:return:
"""
if num is None:
num = 10000
"""每次向下滑动距离10000"""
js = 'scrollTo(0, {})'.format(num)
"""执行滑动"""
self.driver.execute_script(js)
"""滑动之后沉睡等待2秒"""
time.sleep(2)
def parse_page_url(self, num):
"""
执行翻页
:param num: 页码数
"""
"""根据最大页码数的遍历,来执行点击下一页的次数"""
for i in range(int(num) - 2):
"""调用滑动函数"""
self.page_url()
time.sleep(2)
"""下一页的点击事件"""
self.driver.find_element_by_css_selector('#listAll > section.layout-Module.js-ListContent > div.layout-Module-container.layout-Cover.ListContent > div > ul > li.dy-Pagination-next > span').click()
"""每次成功下一页之后,获取其elements内容"""
html_data = self.driver.page_source
html_element = etree.HTML(html_data)
self.save_id_data(html_element)
def save_id_data(self, html_element):
"""
:param html_element: etree转换之后的对象
"""
# 获取直播间的id列表
# html_element = etree.HTML(html_element)
id_num = html_element.xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li/div/a[1]/@href')
# 直播间id去重
# id_num = list(set(id_num))
print(id_num)
# 访问第一个直播间,达到去除窗口的效果
js = 'window.open("{}");'.format('https://www.douyu.com' + id_num[0])
self.driver.execute_script(js)
time.sleep(2)
# 获取所有浏览器窗口
windows = self.driver.window_handles
# 窗口切换,切换到新打开的直播间窗口
self.driver.switch_to.window(windows[1])
# 关闭打开的窗口
self.driver.close()
# 切换到初始窗口
self.driver.switch_to.window(windows[0])
for id in id_num:
# 直播间的url地址拼接
url = 'https://www.douyu.com' + id
# 调用函数
self.requests_url_by(url)
# print(url)
def requests_url_by(self, url):
"""
访问直播
:param url: 直播间的url地址
"""
# js代码执行打开url--直播间的url地址
js = 'window.open("{}");'.format(url)
self.driver.execute_script(js)
# 隐式等待
self.driver.implicitly_wait(10)
# 获取当前所有窗口,返回一个列表
windows = self.driver.window_handles
# 窗口切换,下标操作
self.driver.switch_to.window(windows[1])
try:
for i in range(1, 3):
text = '1'
self.driver.find_element_by_css_selector('.ChatSend-txt').send_keys(text) # 输入弹幕
self.driver.find_element_by_css_selector('.ChatSend-button ').click() # 点击发送弹幕
print(f'弹幕----------已发送----------第{i}次')
time.sleep(1.5) # 每一秒发送一次共发送20次
except:
print('发送失败')
self.driver.close()
self.driver.switch_to.window(windows[0])
def run(self):
num = self.parse_url()
# self.save_id_data(html_element)
self.parse_page_url(num)
if __name__ == '__main__':
dy = DySpider()
dy.run()
这个案例会遍历现在斗鱼开播的直播间,然后在每个直播间发两遍‘1’,但是你需要首先在弹出来斗鱼登陆界面,扫码登陆你的斗鱼账号,不然你是不能发弹幕。该案例中用了很多方法,融汇贯通会很适合。我将在下一期讲解类方法的使用。