爬虫(一)-笔记

爬虫概述

1. 爬虫的概念

模拟浏览器,发送请求,获取响应

网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟客户端(主要指浏览器)发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序。

  • 原则上,只要是客户端(浏览器)能做的事情,爬虫都能够做
  • 爬虫也只能获取客户端(浏览器)所展示出来的数据

2. 爬虫的作用

爬虫在互联网世界中有很多的作用,比如:

  1. 数据采集

    1. 抓取微博评论(机器学习舆情监控)
    2. 抓取招聘网站的招聘信息(数据分析、挖掘)
    3. 新浪滚动新闻
    4. 百度新闻网站
  2. 软件测试

    1. 爬虫之自动化测试
    2. 虫师
  3. 12306抢票

  4. 网站上的投票

    1. 投票网
  5. 网络安全

    1. 短信轰炸

      1. 注册页面1
      2. 注册页面2
      3. 注册页面3
    2. web漏洞扫描

3. 爬虫的分类

3.1 根据被爬取网站的数量不同,可以分为:
  • 通用爬虫,如 搜索引擎
  • 聚焦爬虫,如12306抢票,或专门抓取某一个(某一类)网站数据
3.2 根据是否以获取数据为目的,可以分为:
  • 功能性爬虫,给你喜欢的明星投票、点赞
  • 数据增量爬虫,比如招聘信息
3.3 根据url地址和对应的页面内容是否改变,数据增量爬虫可以分为:
  • 基于url地址变化、内容也随之变化的数据增量爬虫

  • url地址不变、内容变化的数据增量爬虫

在这里插入图片描述

4. 爬虫的流程

爬虫的基本流程如图所示

在这里插入图片描述

  1. 获取一个url
  2. 向url发送请求,并获取响应(需要http协议)
  3. 如果从响应中提取url,则继续发送请求获取响应
  4. 如果从响应中提取数据,则将数据进行保存

http协议

一提起http协议,大家都会想起它是一个应用层协议,那么http协议跟爬虫有什么关系呢?请看下图:

在这里插入图片描述

1. http以及https的概念和区别

HTTPS比HTTP更安全,但是性能更低

  • HTTP:超文本传输协议,默认端口号是80(明文传输)
    • 超文本:是指超过文本,不仅限于文本;还包括图片、音频、视频等文件
    • 传输协议:是指使用共用约定的固定格式来传递转换成字符串的超文本内容
  • HTTPS:HTTP + SSL(安全套接字层),即带有安全套接字层的超本文传输协,默认端口号:443(加密传输)
    • SSL对传输的内容(超文本,也就是请求体或响应体)进行加密
  • 可以打开浏览器访问一个url,右键检查,点击net work,点选一个url,查看http协议的形式

2. 爬虫特别关注的请求头和响应头

2.1 特别关注的请求头字段

在这里插入图片描述

http请求的形式如上图所示,爬虫特别关注以下几个请求头字段

  • Content-Type
  • Host (主机和端口号)
  • Connection (链接类型)
  • Upgrade-Insecure-Requests (升级为HTTPS请求)
  • User-Agent (用户代理提供系统信息和浏览器信息)
  • Referer (页面跳转处,防盗链(图片、视频))
  • Cookie (Cookie,保持会话)
  • Authorization(用于表示HTTP协议中需要认证资源的认证信息,如web中用于jwt认证)
    在这里插入图片描述

加粗的请求头为常用请求头,在服务器被用来进行爬虫识别的频率最高,相较于其余的请求头更为重要,但是这里需要注意的是并不意味这其余的不重要,因为有的网站的运维或者开发人员可能剑走偏锋,会使用一些比较不常见的请求头来进行爬虫的甄别

在这里插入图片描述

2.2 特别关注的响应头字段

在这里插入图片描述

http响应的形式如上图所示,爬虫只关注一个响应头字段

  • Set-Cookie (对方服务器设置cookie到用户浏览器的缓存)

3. 常见的响应状态码

  • 200:成功
  • 302:跳转,新的url在响应的Location头中给出
  • 303:浏览器对于POST的响应进行重定向至新的url
  • 307:浏览器对于GET的响应重定向至新的url
  • 403:资源不可用;服务器理解客户的请求,但拒绝处理它(没有权限,请求非法)
  • 404:找不到该页面
  • 500:服务器内部错误
  • 503:服务器由于维护或者负载过重未能应答,在响应中可能可能会携带Retry-After响应头;有可能是因为爬虫频繁访问url,使服务器忽视爬虫的请求,最终返回503响应状态码

在爬虫中,可能该站点的开发人员或者运维人员为了阻止数据被爬虫轻易获取,可能在状态码上做手脚,也就是说返回的状态码并不一定就是真实情况,比如:服务器已经识别出你是爬虫,但是为了让你疏忽大意,所以照样返回状态码200,但是响应体重并没有数据。

所有的状态码都不可信,一切以是否从抓包得到的响应中获取到数据为准
network 中抓包得到的源码才是判断依据,elements中的源码是渲染之后的源码,不能作为判断标准
在这里插入图片描述

4. 浏览器的运行过程

以下是浏览器发送http发送的过程

在这里插入图片描述

4.1 http请求的过程
  1. 浏览器在拿到域名对应的ip后,先向地址栏中的url发起请求,并获取响应
  2. 在返回的响应内容(html)中,会带有css、js、图片等url地址,以及ajax代码,浏览器按照响应内容中的顺序依次发送其他的请求,并获取相应的响应
  3. 浏览器每获取一个响应就对展示出的结果进行添加(加载),js,css等内容会修改页面的内容,js也可以重新发送请求,获取响应
  4. 从获取第一个响应并在浏览器中展示,直到最终获取全部响应,并在展示的结果中添加内容或修改————这个过程叫做浏览器的渲染
4.2 注意:

但是在爬虫中,爬虫只会请求一次url地址,对应的拿到url地址对应的响应(该响应的内容可以是html,css,js,图片等

浏览器渲染出来的页面和爬虫请求的页面很多时候并不一样,是因为爬虫不具备渲染的能力(当然后续课程中我们会借助其它工具或包来帮助爬虫对响应内容进行渲染)

  • 浏览器最终展示的结果是由多个url地址分别发送的多次请求对应的多次响应共同渲染的结果
  • 所以在爬虫中,需要以发送请求的一个url地址对应的响应为准来进行数据的提取,不进行渲染

5. 关于http协议的其它参考阅读

  • https://blog.csdn.net/qq_33301113/article/category/6943422/2
  • https://www.xuebuyuan.com/3252125.html
  • https://baike.baidu.com/item/http/243074?fr=aladdin
  • https://www.jianshu.com/p/cc1fea7810b2
  • https://blog.csdn.net/qq_30553235/article/details/79282113
  • https://segmentfault.com/q/1010000002403462

requests模块

前面我们了解了爬虫的基础知识,接下来我们来学习如何在代码中实现我们的爬虫

1. requests模块介绍

requests文档http://docs.python-requests.org/zh_CN/latest/index.html

1.1 requests模块的作用:

  • 发送http请求,获取响应数据

1.2 requests模块是一个第三方模块,需要在你的python(虚拟)环境中额外安装

  • pip/pip3 install requests

1.3 requests模块发送get请求

  1. 需求:通过requests向百度首页发送请求,获取该页面的源码

  2. 运行下面的代码,观察打印输出的结果

# 1.2.1-简单的代码实现
import requests 

# 目标url
url = 'https://www.baidu.com' 

# 向目标url发送get请求
response = requests.get(url)

# 打印响应内容
print(response.text)

2. response响应对象

观察上边代码运行结果发现,有好多乱码;这是因为编解码使用的字符集不同早造成的;我们尝试使用下边的办法来解决中文乱码问题

# 1.2.2-response.content
import requests 

# 目标url
url = 'https://www.baidu.com' 

# 向目标url发送get请求
response = requests.get(url)

# 打印响应内容
# response.text存储的是bytes类型的响应源码,可以进行decode操作
# print(response.text)
# 打印相应 编码格式
# print(response.encoding)  # ISO-8859-1
print(response.content.decode()) # 注意这里!


"""
response.encoding = 'utf8'
print(response.text)
# 等效于	
print(response.content.decode())
"""
  1. response.text是requests模块按照chardet模块推测出的编码字符集进行解码的结果
  2. 网络传输的字符串都是bytes类型的,所以response.text = response.content.decode(‘推测出的编码字符集’)
  3. 我们可以在网页源码中搜索charset,尝试参考该编码字符集,注意存在不准确的情况

2.1 response.text 和response.content的区别:

  • response.text
    • 类型:str
    • 解码类型: requests模块自动根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码
  • response.content
    • 类型:bytes
    • 解码类型: 没有指定

知识点:掌握 response.text和response.content的区别

2.2 通过对response.content进行decode,来解决中文乱码

  • response.content.decode() 默认utf-8
  • response.content.decode("GBK")
  • 常见的编码字符集
    • utf-8
    • gbk
    • gb2312
    • ascii
    • iso-8859-1

2.3 response响应对象的其它常用属性或方法

response = requests.get(url)中response是发送请求获取的响应对象;response响应对象中除了text、content获取响应内容以外还有其它常用的属性或方法:

  • response.url响应的url;有时候响应的url和请求的url并不一致
  • response.status_code 响应状态码
  • response.request.headers 响应对应的请求头
  • response.headers 响应头
  • response.request._cookies 响应对应请求的cookie;返回cookieJar类型
  • response.cookies 响应的cookie(经过了set-cookie动作;返回cookieJar类型
  • response.json()自动将json字符串类型的响应内容转换为python对象(dict or list)
# 1.2.3-response其它常用属性
import requests

# 目标url
url = 'https://www.baidu.com'

# 向目标url发送get请求
response = requests.get(url)

# 打印响应内容
# print(response.text)
# print(response.content.decode()) 			# 注意这里!
print(response.url)							# 打印响应的url
print(response.status_code)					# 打印响应的状态码
print(response.request.headers)				# 打印响应对象的请求头
print(response.headers)						# 打印响应头
print(response.request._cookies)			# 打印请求携带的cookies
print(response.cookies)						# 打印响应中携带的cookies

3. requests模块发送请求

3.1 发送带header的请求

我们先写一个获取百度首页的代码

import requests

url = 'https://www.baidu.com'

response = requests.get(url)

print(response.content.decode())

# 打印响应对应请求的请求头信息
print(response.request.headers)
3.1.1 思考
  1. 对比浏览器上百度首页的网页源码和代码中的百度首页的源码,有什么不同?

    • 查看网页源码的方法:
      • 右键-查看网页源代码 或
      • 右键-检查
  2. 对比对应url的响应内容和代码中的百度首页的源码,有什么不同?

    • 查看对应url的响应内容的方法:
      1. 右键-检查
      2. 点击 Net work
      3. 勾选 Preserve log
      4. 刷新页面
      5. 查看Name一栏下和浏览器地址栏相同的url的Response
  3. 代码中的百度首页的源码非常少,为什么?

    • 需要我们带上请求头信息

      回顾爬虫的概念,模拟浏览器,欺骗服务器,获取和浏览器一致的内容

    • 请求头中有很多字段,其中User-Agent字段必不可少,表示客户端的操作系统以及浏览器的信息

3.1.2 携带请求头发送请求的方法

requests.get(url, headers=headers)

  • headers参数接收字典形式的请求头
  • 请求头字段名作为key,字段对应的值作为value
3.1.3 完成代码实现

从浏览器中复制User-Agent,构造headers字典;完成下面的代码后,运行代码查看结果

import requests

url = 'https://www.baidu.com'

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

# 在请求头中带上User-Agent,模拟浏览器发送请求
response = requests.get(url, headers=headers) 

print(response.content)

# 打印请求头信息
print(response.request.headers)

3.2 发送带参数的请求

我们在使用百度搜索的时候经常发现url地址中会有一个 ?,那么该问号后边的就是请求参数,又叫做查询字符串

3.2.1 在url携带参数

直接对含有参数的url发起请求

import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

url = 'https://www.baidu.com/s?wd=python'

response = requests.get(url, headers=headers)

3.2.2 通过params携带参数字典

​ 1.构建请求参数字典

​ 2.向接口发送请求的时候带上参数字典,参数字典设置给params

import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

# 这是目标url
# url = 'https://www.baidu.com/s?wd=python'

# 最后有没有问号结果都一样
url = 'https://www.baidu.com/s?'

# 请求参数是一个字典 即wd=python
kw = {'wd': 'python'}

# 带上请求参数发起请求,获取响应
response = requests.get(url, headers=headers, params=kw)

print(response.content)

3.3 在headers参数中携带cookie

网站经常利用请求头中的Cookie字段来做用户访问状态的保持,那么我们可以在headers参数中添加Cookie,模拟普通用户的请求。我们以github登陆为例:

3.3.1 github登陆抓包分析
  1. 打开浏览器,右键-检查,点击Net work,勾选Preserve log
  2. 访问github登陆的url地址 https://github.com/login
  3. 输入账号密码点击登陆后,访问一个需要登陆后才能获取正确内容的url,比如点击右上角的Your profile访问https://github.com/USER_NAME
  4. 确定url之后,再确定发送该请求所需要的请求头信息中的User-Agent和Cookie

在这里插入图片描述

3.3.2 完成代码
  • 从浏览器中复制User-Agent和Cookie
  • 浏览器中的请求头字段和值与headers参数中必须一致
  • headers请求参数字典中的Cookie键对应的值是字符串
import requests

url = 'https://github.com/USER_NAME'

# 构造请求头字典
headers = {
    # 从浏览器中复制过来的User-Agent
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36',
    # 从浏览器中复制过来的Cookie
    'Cookie': 'xxx这里是复制过来的cookie字符串'
}

# 请求头参数字典中携带cookie字符串
resp = requests.get(url, headers=headers)

print(resp.text)
3.3.3 运行代码验证结果

在打印的输出结果中搜索title,html中的标题文本内容如果是你的github账号,则成功利用headers参数携带cookie,获取登陆后才能访问的页面

在这里插入图片描述

3.4 cookies参数的使用

上一小节我们在headers参数中携带cookie,也可以使用专门的cookies参数

  1. cookies参数的形式:字典

    cookies = {"cookie的name":"cookie的value"}

    • 该字典对应请求头中Cookie字符串,以分号、空格分割每一对字典键值对
    • 等号左边的是一个cookie的name,对应cookies字典的key
    • 等号右边对应cookies字典的value
  2. cookies参数的使用方法

    response = requests.get(url, cookies)

  3. 将cookie字符串转换为cookies参数所需的字典:

    cookies_dict = {cookie.split('=')[0]:cookie.split('=')[-1] for cookie in cookies_str.split('; ')}

  4. 注意:cookie一般是有过期时间的,一旦过期需要重新获取

import requests

url = 'https://github.com/USER_NAME'

# 构造请求头字典
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'
}
# 构造cookies字典
cookies_str = '从浏览器中copy过来的cookies字符串'

cookies_dict = {cookie.split('=')[0]:cookie.split('=')[-1] for cookie in cookies_str.split('; ')}

# 请求头参数字典中携带cookie字符串
resp = requests.get(url, headers=headers, cookies=cookies_dict)

print(resp.text)
# 构造cookies字典
temp = '从浏览器中copy过来的cookies字符串'
# 普通方法构建cookie key:value
cookie_list = temp.split(';')
print('cookie_list',cookie_list)
cookies = {}
for cookie in cookie_list:
    cookies[cookie.split('=')[0]] = cookie.split('=')[1]
print('gen_cookies', cookies)

# 字典推导式
cookies = {cookie.split('=')[0]:cookie.split('=')[1] for cookie in temp.split(';')}
print('dict_cookies', cookies)

3.5 cookieJar对象转换为cookies字典的方法

使用requests获取的resposne对象,具有cookies属性。该属性值是一个cookieJar类型,包含了对方服务器设置在本地的cookie。我们如何将其转换为cookies字典呢?

cookiejar

import requests

url = 'https://www.baidu.com'
response = requests.get(url)
print('cookiejar', response.cookies)

dict_cookies = requests.utils.dict_from_cookiejar(response.cookies)
print('dict_cookies', dict_cookies)

jar_cookies = requests.utils.cookiejar_from_dict(dict_cookies)
# 从cookie_dict转换为cookiejar,会丢失域名
print('jar_cookies', jar_cookies)
cookiejar <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
dict_cookies {'BDORZ': '27315'}
jar_cookies <RequestsCookieJar[<Cookie BDORZ=27315 for />]>
  1. 转换方法

    cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)

  2. 其中response.cookies返回的就是cookieJar类型的对象

  3. requests.utils.dict_from_cookiejar函数返回cookies字典

3.6 超时参数timeout的使用

在平时网上冲浪的过程中,我们经常会遇到网络波动,这个时候,一个请求等了很久可能任然没有结果。

在爬虫中,一个请求很久没有结果,就会让整个项目的效率变得非常低,这个时候我们就需要对请求进行强制要求,让他必须在特定的时间内返回结果,否则就报错。

  1. 超时参数timeout的使用方法(默认为180s)

    response = requests.get(url, timeout=3)

  2. timeout=3表示:发送请求后,3秒钟内返回响应,否则就抛出异常

import requests


url = 'https://twitter.com'
response = requests.get(url, timeout=3)     # 设置超时时间

3.7 了解代理以及proxy代理参数的使用

proxy代理参数通过指定代理ip,让代理ip对应的正向代理服务器转发我们发送的请求,那么我们首先来了解一下代理ip以及代理服务器

3.7.1 理解使用代理的过程
  1. 代理ip是一个ip,指向的是一个代理服务器
  2. 代理服务器能够帮我们向目标服务器转发请求

在这里插入图片描述

3.7.2 正向代理和反向代理的区别

前边提到proxy参数指定的代理ip指向的是正向的代理服务器,那么相应的就有反向服务器;现在来了解一下正向代理服务器和反向代理服务器的区别

  1. 从发送请求的一方的角度,来区分正向或反向代理
  2. 为浏览器或客户端(发送请求的一方)转发请求的,叫做正向代理
    • 浏览器知道最终处理请求的服务器的真实ip地址,例如VPN
  3. 不为浏览器或客户端(发送请求的一方)转发请求、而是为最终处理请求的服务器转发请求的,叫做反向代理
    • 浏览器不知道服务器的真实地址,例如nginx(负载均衡,当收到请求时,将请求根据设置转发给相应的后台服务器进行处理)
3.7.3 代理ip(代理服务器)的分类
  1. 根据代理ip的匿名程度,代理IP可以分为下面三类:

    • 透明代理(Transparent Proxy):透明代理虽然可以直接“隐藏”你的IP地址,但是还是可以查到你是谁。目标服务器接收到的请求头如下:

      REMOTE_ADDR = Proxy IP
      HTTP_VIA = Proxy IP
      HTTP_X_FORWARDED_FOR = Your IP
      
    • 匿名代理(Anonymous Proxy):使用匿名代理,别人只能知道你用了代理,无法知道你是谁。目标服务器接收到的请求头如下:

      REMOTE_ADDR = proxy IP
      HTTP_VIA = proxy IP
      HTTP_X_FORWARDED_FOR = proxy IP
      
    • 高匿代理(Elite proxy或High Anonymity Proxy):高匿代理让别人根本无法发现你是在用代理,所以是最好的选择。毫无疑问使用高匿代理效果最好。目标服务器接收到的请求头如下:

      REMOTE_ADDR = Proxy IP
      HTTP_VIA = not determined
      HTTP_X_FORWARDED_FOR = not determined
      
  2. 根据网站所使用的协议不同,需要使用相应协议的代理服务。从代理服务请求使用的协议可以分为:

    • http代理:目标url为http协议
    • https代理:目标url为https协议
    • socks隧道代理(例如socks5代理)等:
      1. socks 代理只是简单地传递数据包,不关心是何种应用协议(FTP、HTTP和HTTPS等)。
      2. socks 代理比http、https代理耗时少。
      3. socks 代理可以转发http和https的请求
3.7.4 proxies代理参数的使用

为了让服务器以为不是同一个客户端在请求;为了防止频繁向一个域名发送请求被封ip,所以我们需要使用代理ip;那么我们接下来要学习requests模块是如何使用代理ip的

  • 用法:

    response = requests.get(url, proxies=proxies)
    
  • proxies的形式:字典

  • 例如:

    proxies = { 
    		# http代理只能用于http
        "http": "http://12.34.56.79:9527", 
        # https 可以用于https和绝大多数的http
        "https": "https://12.34.56.79:9527", 
    }
    
import requests


url = 'http://www.baidu.com'

# 设置代理
# https://www.kuaidaili.com/free/
# http://proxy.mimvp.com/freeopen
proxies = {
    'http':'120.194.55.139:6969',
    # 'https':'120.194.55.139:6969'
}
response = requests.get(url, proxies = proxies)
print(response.text)

  • 注意:如果proxies字典中包含有多个键值对,发送请求时将按照url地址的协议来选择使用相应的代理ip
  • 如果代理使用成功,不会有任何报错,能成功获取响应;如果失败,要么阻塞,要么报错。

3.8 使用verify参数忽略CA证书

在使用浏览器上网的时候,有时能够看到下面的提示(2018年10月之前的12306网站):

在这里插入图片描述

3.8.1 运行代码查看代码中向不安全的链接发起请求的效果

运行下面的代码将会抛出包含ssl.CertificateError ...字样的异常

import requests
url = "https://sam.huat.edu.cn:8443/selfservice/"
response = requests.get(url)
3.8.2 解决方案

为了在代码中能够正常的请求,我们使用verify=False参数,此时requests模块发送请求将不做CA证书的验证:verify参数能够忽略CA证书的认证

import requests
url = "https://sam.huat.edu.cn:8443/selfservice/" 
response = requests.get(url,verify=False)

4. requests模块发送post请求

思考:哪些地方我们会用到POST请求?

  1. 登录注册( 在web工程师看来POST 比 GET 更安全,url地址中不会暴露用户的账号密码等信息)
  2. 需要传输大文本内容的时候( POST 请求对数据长度没有要求)

所以同样的,我们的爬虫也需要在这两个地方回去模拟浏览器发送post请求

4.1 requests发送post请求的方法

  • response = requests.post(url, data)

  • data参数接收一个字典

  • requests模块发送post请求函数的其它参数和发送get请求的参数完全一致

4.2 POST请求练习

下面面我们通过金山翻译的例子看看post请求如何使用:

  1. 地址:http://fy.iciba.com/

    思路分析
    1. 抓包确定请求的url地址

      在这里插入图片描述

    2. 确定请求的参数

      在这里插入图片描述

    3. 确定返回数据的位置

      在这里插入图片描述

    4. 模拟浏览器获取数据

有道翻译
import requests
import json


class Translate_BD(object):
    def __init__(self, word):
        self.word = word
        self.url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36",
           }

        self.data = {
            "i": self.word,
            "from": "AUTO",
            "to": "AUTO",
            "smartresult": "dict",
            "client": "fanyideskweb",""
            "salt": "16209157389175",
            "sign": "582141110a9b06ba0a34724c5ccec29c",
            "lts": "1620915738917",
            "bv": "f52186f8c76a0fbf1baf7e6da04928ea",
            "doctype": "json",
            "version": "2.1",
            "keyfrom": "fanyi.web",
            "action": "FY_BY_REALTlME",
        }


    def get_data(self):
        # 获取响应内容
        response = requests.post(url = self.url, data = self.data, headers = self.headers)
        return response.content  # bytes类型


    def parse_data(self, data):
        # json转换为dict
        dict_json = json.loads(data)
        # dict_json:{"type":"ZH_CN2EN","errorCode":0,"elapsedTime":0,"translateResult":[[{"src":"\xe4\xbd\xa0\xe5\xa5\xbd","tgt":"hello"}]]}
        # print(dict_json['translateResult'][0][0]['tgt'])  # hello
        return dict_json['translateResult'][0][0]['tgt']


    def run(self):
        # 编写爬虫逻辑
        # 1.url
        # 2.headers
        # 3.data字典,即发送数据
        # 4.发送请求获取响应
        response = self.get_data()
        # print(response)

        # 5.解析json字符串,即数据解析
        result = self.parse_data(response)
        print('>>', result)


if __name__ == "__main__":
    kw = input('请输入翻译内容>>:')
    trans = Translate_BD(kw)
    trans.run()

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

post数据来源

在这里插入图片描述
包含:

  1. 固定值 抓包比较不变的值 如 from to

  2. 输入值 抓包比较,根据自身变化的值 如query

  3. 预设值

    1. 静态文件中预设值 需要提前从静态html文件中获取,通过正则等方法
      在这里插入图片描述
      在这里插入图片描述

    2. 发请求 需要对指定地址发送请求获取数据
      在这里插入图片描述
      在这里插入图片描述
      salt 和 ts 是时间戳,发送请求时候生成

  4. 在客户端生成 分析js,模拟生成数据
    上述 sign则是在客户端生成数据,需要分析js来获取

4.2.3 抓包分析的结论
  1. url地址:http://fy.iciba.com/

  2. 请求方法:POST

  3. 请求所需参数:

    data = {
        'f': 'auto', # 表示被翻译的语言是自动识别
        't': 'auto', # 表示翻译后的语言是自动识别
        'w': '人生苦短' # 要翻译的中文字符串
    }
    
  4. pc端User-Agent:

    Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36

4.2.4 代码实现

了解requests模块发送post请求的方法,以及分析过移动端的百度翻译之后,我们来完成代码

import requests
import json


class King(object):

    def __init__(self, word):
        self.url = "http://fy.iciba.com/ajax.php?a=fy"
        self.word = word
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
        }
        self.post_data = {
            "f": "auto",
            "t": "auto",
            "w": self.word
        }

    def get_data(self):
        response = requests.post(self.url, headers=self.headers, data=self.post_data)
        # 默认返回bytes类型,除非确定外部调用使用str才进行解码操作
        return response.content

    def parse_data(self, data):

        # 将json数据转换成python字典
        dict_data = json.loads(data)

        # 从字典中抽取翻译结果
        try:
            print(dict_data['content']['out'])
        except:
            print(dict_data['content']['word_mean'][0])

    def run(self):
        # url
        # headers
        # post——data
        # 发送请求
        data = self.get_data()
        # 解析
        self.parse_data(data)

if __name__ == '__main__':
    # king = King("人生苦短,及时行乐")
    king = King("China")
    king.run()
    # python标准库有很多有用的方法,每天看一个标准库的使用

5. 利用requests.session进行状态保持

requests模块中的Session类能够自动处理发送请求获取响应过程中产生的cookie,进而达到状态保持的目的。

5.1 requests.session的作用以及应用场景

  • requests.session的作用
    • 自动处理cookie,即 下一次请求会带上前一次的cookie
  • requests.session的应用场景
    • 自动处理连续的多次请求过程中产生的cookie

5.2 requests.session使用方法

session实例在请求了一个网站后,对方服务器设置在本地的cookie会保存在session中,下一次再使用session请求对方服务器的时候,会带上前一次的cookie

session = requests.session() # 实例化session对象
response = session.get(url, headers, ...)
response = session.post(url, data, ...)
  • session对象发送get或post请求的参数,与requests模块发送请求的参数完全一致

5.3 测试

使用requests.session来完成github登陆,并获取需要登陆后才能访问的页面

5.3.1 提示
  1. 对github登陆以及访问登陆后才能访问的页面的整个完成过程进行抓包
  2. 确定登陆请求的url地址、请求方法和所需的请求参数
    • 部分请求参数在别的url对应的响应内容中,可以使用re模块获取
  3. 确定登陆后才能访问的页面的的url地址和请求方法
  4. 利用requests.session完成代码

在这里插入图片描述
当输入账号密码登录后,有些页面会重定向至index,导致当前页面抓的包丢失,若要保留会话,则勾选preserve log
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
两次抓包对比,token,timestamp, stamp_secret为预设值,可以在login的response中获取(记得勾选preserve log 否则重定向后,无login包)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

验证登录成功

通过profile的登录地址
在这里插入图片描述

    # url3 验证 -profile的url
    url3 = 'https://github.com/Vinsmoke-Joker'
    response3 = session.get(url3)
    with open('github_login.html', 'wb') as f:
        f.write(response3.content)

在这里插入图片描述

完成代码参考
import requests
import re


def login():
    # session对象
    session = requests.session()
    # hearders
    session.hearders = {
        'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'
    }
    
    # url1 获取token
    url1 = 'https://github.com/login'
    # 发送请求获取响应,re只能对str进行,因此进行decode()
    response1 = session.get(url1).content.decode()
    # 正则提取
    # """< input type = "hidden" name = "authenticity_token" value = "nqk56sI1P2tGKhxejiyFguvM8JcIdPz9ud1sqGEysbkKfSWEngzCxgy7tnegfWA3Z8VVMqhEujqqhlXaXW2svg==" / >"""
    token = re.findall('<input type="hidden" name="authenticity_token" value="(.*?)"', response1)
    timestamp = re.findall('<input type="hidden" name="timestamp" value="(.*?)"', response1)
    timestamp_secret = re.findall('<input type="hidden" name="timestamp_secret" value="(.*?)"', response1)
    print('token', token) # pBnhr8jTY9vfl3IqOIJoCihKfzfTYFJDp8D+9if6eyXcFzeGWk9PbgKa9jQ14ni14pAGffp4uktnrfsZRCIrsg==
    print('timestamp', timestamp) # 1621262205326
    print('timestamp_secret', timestamp_secret) # 6f9599a56f2c6b1125981ab611ec9d1b676feb0c634b3949dfa8ef6665f02cad

    # url2 登录
    url2 = 'https://github.com/session'
    # 构建表单数据
    data ={
        'commit': 'Sign in',
        'authenticity_token': token,
        'login': 'github 账号',
        'password': 'github密码',
        #'trusted_device': ,
        'webauthn-support': 'supported',
        'webauthn-iuvpaa-support': 'unsupported',
        # 'return_to':
        # 'allow_signup':
        # 'client_id':
        # 'integration':,
        # 'required_field_1cec':
        'timestamp': timestamp,
        'timestamp_secret': timestamp_secret
    }
    # 发送请求登录
    session.post(url2, data = data)

    # url3 验证 -profile的url
    url3 = 'https://github.com/Vinsmoke-Joker'
    response3 = session.get(url3)
    with open('github_login.html', 'wb') as f:
        f.write(response3.content)

if __name__ == '__main__':
    login()
5.3.2 参考代码
import requests
import re


# 构造请求头字典
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36',
}

# 实例化session对象
session = requests.session()

# 访问登陆页获取登陆请求所需参数
response = session.get('https://github.com/login', headers=headers)
authenticity_token = re.search('name="authenticity_token" value="(.*?)" />', response.text).group(1) # 使用正则获取登陆请求所需参数

# 构造登陆请求参数字典
data = {
    'commit': 'Sign in', # 固定值
    'utf8': '✓', # 固定值
    'authenticity_token': authenticity_token, # 该参数在登陆页的响应内容中
    'login': input('输入github账号:'),
    'password': input('输入github账号:')
}

# 发送登陆请求(无需关注本次请求的响应)
session.post('https://github.com/session', headers=headers, data=data)

# 打印需要登陆后才能访问的页面
response = session.get('https://github.com/1596930226', headers=headers)
print(response.text)

数据提取概述

1. 响应内容的分类

在发送请求获取响应之后,可能存在多种不同类型的响应内容;而且很多时候,我们只需要响应内容中的一部分数据

  • 结构化的响应内容

    • json字符串

      • 可以使用re、json等模块来提取特定数据
      • json字符串的例子如下

    • xml字符串

      • 可以使用re、lxml等模块来提取特定数据

      • xml字符串的例子如下

        <bookstore>
        <book category="COOKING">
          <title lang="en">Everyday Italian</title> 
          <author>Giada De Laurentiis</author> 
          <year>2005</year> 
          <price>30.00</price> 
        </book>
        <book category="CHILDREN">
          <title lang="en">Harry Potter</title> 
          <author>J K. Rowling</author> 
          <year>2005</year> 
          <price>29.99</price> 
        </book>
        <book category="WEB">
          <title lang="en">Learning XML</title> 
          <author>Erik T. Ray</author> 
          <year>2003</year> 
          <price>39.95</price> 
        </book>
        </bookstore>
        
  • 非结构化的响应内容

    • html字符串

      • 可以使用re、lxml等模块来提取特定数据
      • html字符串的例子如下图
        在这里插入图片描述

2. 认识xml以及和html的区别

2.1 认识xml

xml是一种可扩展标记语言,样子和html很像,功能更专注于对传输和存储数据

<bookstore>
<book category="COOKING">
  <title lang="en">Everyday Italian</title> 
  <author>Giada De Laurentiis</author> 
  <year>2005</year> 
  <price>30.00</price> 
</book>
<book category="CHILDREN">
  <title lang="en">Harry Potter</title> 
  <author>J K. Rowling</author> 
  <year>2005</year> 
  <price>29.99</price> 
</book>
<book category="WEB">
  <title lang="en">Learning XML</title> 
  <author>Erik T. Ray</author> 
  <year>2003</year> 
  <price>39.95</price> 
</book>
</bookstore>

上面的xml内容可以表示为下面的树结构:

在这里插入图片描述

2.2 xml和html的区别

二者区别如下图

在这里插入图片描述

  • html:
    • 超文本标记语言
    • 为了更好的显示数据,侧重点是为了显示
  • xml:
    • 可扩展标记语言
    • 为了传输和存储数据,侧重点是在于数据内容本身

2.3 常用数据解析方法

在这里插入图片描述

数据提取-jsonpath模块

1. jsonpath模块的使用场景

data = {'key1' : {'key2' : {'key3' : {'key4' : {'key5' : {'key6' : 'python'}}}}}}
print(data['key1']['key2']['key3']['key4']['key5']['key6'])

如果有一个多层嵌套的复杂字典,想要根据key和下标来批量提取value,这是比较困难的。jsonpath模块就能解决这个痛点
jsonpath可以按照key对python字典进行批量数据提取

2. jsonpath模块的使用方法

2.1 jsonpath模块的安装

jsonpath是第三方模块,需要额外安装

pip install jsonpath

2.2 jsonpath模块提取数据的方法

from jsonpath import jsonpath
ret = jsonpath(a, 'jsonpath语法规则字符串')

2.3 jsonpath语法规则

在这里插入图片描述

from jsonpath import jsonpath


data = {'key1' : {'key2' : {'key3' : {'key4' : {'key5' : {'key6' : 'python'}}}}}}
print(data['key1']['key2']['key3']['key4']['key5']['key6']) # python

# 注意:jsonpath结果为一个list
print(jsonpath(data, '$.key1.key2.key3.key4.key5.key6')) # ['python']
print(jsonpath(data, '$..key6')) # ['python']
2.4 jsonpath使用示例
from jsonpath import jsonpath
import json


book_dict = """{
  "store": {
    "book": [
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}"""

# 将requests获得的 string转换json
book_dict = json.loads(book_dict)
print('所有颜色:', jsonpath(book_dict, '$..color'))  # 所有颜色: ['red']
print('所有价格和:', sum(jsonpath(book_dict, '$..price')))   # 所有价格: 73.87

在这里插入图片描述

3. jsonpath练习

我们以拉勾网城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例,获取所有城市的名字的列表,并写入文件。

参考代码:

import requests
import jsonpath
import json

# 获取拉勾网城市json字符串
url = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json'
headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"}
response =requests.get(url, headers=headers)
html_str = response.content.decode()

# 把json格式字符串转换成python对象
jsonobj = json.loads(html_str)

# 从根节点开始,获取所有key为name的值
citylist = jsonpath.jsonpath(jsonobj,'$..name')

# 写入文件
with open('city_name.txt','w') as f:
    content = json.dumps(citylist, ensure_ascii=False)
    f.write(content)

数据提取-lxml模块

1. 了解 lxml模块和xpath语法

对html或xml形式的文本提取特定的内容,就需要我们掌握lxml模块的使用和xpath语法。

  • lxml模块可以利用XPath规则语法,来快速的定位HTML\XML 文档中特定元素以及获取节点信息(文本内容、属性值)
  • XPath (XML Path Language) 是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历
  • 提取xml、html中的数据需要lxml模块和xpath语法配合使用

2. 谷歌浏览器xpath helper插件的安装和使用

要想利用lxml模块提取数据,需要我们掌握xpath语法规则。接下来我们就来了解一下xpath helper插件,它可以帮助我们练习xpath语法

2.1 谷歌浏览器xpath helper插件的作用

在谷歌浏览器中对当前页面测试xpath语法规则

2.2 谷歌浏览器xpath helper插件的安装和使用

我们以windos为例进行xpath helper的安装

2.2.1 xpath helper插件的安装
  1. 下载Chrome插件 XPath Helper

  2. 把文件的后缀名crx改为rar,然后解压

  3. 将包含所有解压好的文件的文件夹拖入到已经开启开发者模式的chrome浏览器扩展程序界面

    在这里插入图片描述

    在这里插入图片描述

  4. 重启浏览器后,访问url之后在页面中点击xpath图标,就可以使用了
    在这里插入图片描述

在这里插入图片描述

  1. 如果是linux或macOS操作系统,无需操作上述的步骤2,直接将crx文件拖入已经开启开发者模式的chrome浏览器扩展程序界面

3. xpath的节点关系

学习xpath语法需要先了解xpath中的节点关系

3.1 xpath中的节点是什么

每个html、xml的标签我们都称之为节点,其中最顶层的节点称为根节点。我们以xml为例,html也是一样的

在这里插入图片描述

3.2 xpath中节点的关系

在这里插入图片描述

authortitle的第一个兄弟节点

4. xpath语法-基础节点选择语法

  1. XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
  2. 这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
  3. 使用chrome插件选择标签时候,选中时,选中的标签会添加属性class="xh-highlight"

在这里插入图片描述

4.1 xpath定位节点以及提取属性或文本内容的语法

表达式描述
nodename选中该元素。
/从根节点选取、或者是元素和元素间的过渡。(绝对路径)
//从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 (相对路径)
.选取当前节点。
选取当前节点的父节点。
@选取属性。(永远添加在最后一个节点末尾,作数据提取)
text()选取文本。 (永远添加在最后一个节点末尾,作数据提取)

4.2 语法练习

接下来我们通过itcast的页面来练习上述语法:http://www.itcast.cn/

  • 选择所有的h2下的文本
    • //h2/text()
  • 获取所有的a标签的href
    • //a/@href
  • 获取html下的head下的title的文本
    • /html/head/title/text()
  • 获取html下的head下的link标签的href
    • /html/head/link/@href

5. xpath语法-节点修饰语法

可以根据标签的属性值、下标等来获取特定的节点

5.1 节点修饰语法

路径表达式结果
//title[@lang=“eng”]选择lang属性值为eng的所有title元素
/bookstore/book[1]选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()]选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1]选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()>1]选择bookstore下面的book元素,从第二个开始选择 (position()为区间选择,position()代表所有)
//book/title[text()=‘Harry Potter’]选择所有book下的title元素,仅仅选择文本为Harry Potter的title元素
/bookstore/book[price>35.00]/title选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
节点修饰语法示例
  1. 通索引修饰节点
 /bookstore/book[1]   
 /bookstore/book[last()]  
  1. 通过属性值修饰节点
    出现在[]中的@是使用标签属性名和属性值修饰节点
    在这里插入图片描述
    出现在末尾的/@是取属性值
    在这里插入图片描述
  2. 通过子节点的值修饰节点
    在这里插入图片描述
  3. 通过包含修饰
//div[contains(@id, 'qiushi_tag_')]
//span[contains(text(), '下一页')]

在这里插入图片描述
在这里插入图片描述

5.2 关于xpath的下标

  • 在xpath中,第一个元素的位置是1
  • 最后一个元素的位置是last()
  • 倒数第二个是last()-1

5.3 语法练习

从itcast的页面中,选择所有学科的名称、第一个学科的链接、最后一个学科的链接:http://www.itcast.cn/

  • 所有的学科的名称
    • //div[@class="nav_txt"]//a[@class="a_gd"]
  • 第一个学科的链接
    • //div[@class="nav_txt"]/ul/li[1]/a/@href
  • 最后一个学科的链接
    • //div[@class="nav_txt"]/ul/li[last()]/a/@href

6. xpath语法-其他常用节点选择语法

可以通过通配符来选取未知的html、xml的元素

6.1 选取未知节点的语法

通配符描述
*匹配任何元素节点。
@*匹配任何属性节点。
node()匹配任何类型的节点。

在这里插入图片描述
通配所有属性
在这里插入图片描述
xpath 复合使用 – |
在这里插入图片描述

6.2 语法练习

从itcast的页面中 http://www.itcast.cn/ ,选中全部的标签、全部的属性

  • 全部的标签
    • //*
  • 全部的属性
    • //node()

7. lxml模块的安装与使用示例

lxml模块是一个第三方模块,安装之后使用

7.1 lxml模块的安装

对发送请求获取的xml或html形式的响应内容进行提取

pip/pip3 install lxml

7.2 爬虫对html提取的内容

  • 提取标签中的文本内容
  • 提取标签中的属性的值
    • 比如,提取a标签中href属性的值,获取url,进而继续发起请求

7.3 lxml模块的使用

  1. 导入lxml 的 etree 库

    from lxml import etree

  2. 利用etree.HTML,将html字符串(bytes类型或str类型)转化为Element对象,Element对象具有xpath的方法,返回结果的列表

    html = etree.HTML(text) 
    ret_list = html.xpath("xpath语法规则字符串")
    
  3. xpath方法返回列表的三种情况

    • 返回空列表:根据xpath语法规则字符串,没有定位到任何元素
    • 返回由字符串构成的列表:xpath字符串规则匹配的一定是文本内容或某属性的值
    • 返回由Element对象构成的列表:xpath规则字符串匹配的是标签,列表中的Element对象可以继续进行xpath

7.4 lxml模块使用示例

运行下面的代码,查看打印的结果

from lxml import etree
text = ''' 
<div> 
  <ul> 
    <li class="item-1">
      <a href="link1.html">first item</a>
    </li> 
    <li class="item-1">
      <a href="link2.html">second item</a>
    </li> 
    <li class="item-inactive">
      <a href="link3.html">third item</a>
    </li> 
    <li class="item-1">
      <a href="link4.html">fourth item</a>
    </li> 
    <li class="item-0">
      a href="link5.html">fifth item</a>
  </ul> 
</div>
'''
# 获取element 对象
html = etree.HTML(text)
# print(html)  # <Element html at 0x2a6259133c8>
#获取href的列表和title的列表
href_list = html.xpath("//li[@class='item-1']/a/@href")
title_list = html.xpath("//li[@class='item-1']/a/text()")

#组装成字典
#for href in href_list:
#    item = {}
#    item["href"] = href
#    item["title"] = title_list[href_list.index(href)]
#    print(item)
# 或
#组装成字典
for href, title in zip(href_list, title_list):
   item = {}
   item["href"] = href
   item["title"] = title
   print(item)
# item = {k:v for k, v in zip(href_list, title_list)}

在这里插入图片描述

for el in html.xpath('//a'):
    print(el)
    # //相对于整个文档 提取了所有文本 , / 绝对路径的开始根节点
    print(el.xpath('//text()'))
    print(el.xpath('.//text()'))
    print(el.xpath('./text()'))
    print(el.xpath('text()'))
    print(el.xpath('.//text()')[0], el.xpath('.//@href')[0])
    break
<Element a at 0x1e597e1e708>
[' \n  ', ' \n    ', '\n      ', 'first item', '\n    ', ' \n    ', '\n      ', 'second item', '\n    ', ' \n    ', '\n      ', 'third item', '\n    ', ' \n    ', '\n      ', 'fourth item', '\n    ', ' \n    ', '\n      a href="link5.html">fifth item\n  ', ' \n', '\n']
['first item']
['first item']
['first item']
first item link1.html

8 练习

将下面的html文档字符串中,将每个class为item-1的li标签作为1条新闻数据。提取a标签的文本内容以及链接,组装成一个字典。

text = ''' <div> <ul> 
        <li class="item-1"><a>first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''
  • 注意:

    • 先分组,再提取数据,可以避免数据的错乱

    • 对于空值要进行判断

  • 参考代码

  from lxml import etree
  text = ''' 
<div> 
  <ul> 
    <li class="item-1">
      <a href="link1.html">first item</a>
    </li> 
    <li class="item-1">
      <a href="link2.html">second item</a>
    </li> 
    <li class="item-inactive">
      <a href="link3.html">third item</a>
    </li> 
    <li class="item-1">
      <a href="link4.html">fourth item</a>
    </li> 
    <li class="item-0">
      a href="link5.html">fifth item</a>
  </ul> 
</div>'''
  
  #根据li标签进行分组
  html = etree.HTML(text)
  li_list = html.xpath("//li[@class='item-1']")
  
  #在每一组中继续进行数据的提取
  for li in li_list:
      item = {}
      item["href"] = li.xpath("./a/@href")[0] if len(li.xpath("./a/@href"))>0 else None
      item["title"] = li.xpath("./a/text()")[0] if len(li.xpath("./a/text()"))>0 else None
      print(item)

10. lxml模块中etree.tostring函数的使用

运行下边的代码,观察对比html的原字符串和打印输出的结果

from lxml import etree
html_str = ''' <div> <ul> 
        <li class="item-1"><a href="link1.html">first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

html = etree.HTML(html_str)

handeled_html_str = etree.tostring(html).decode()
print(handeled_html_str)

10.1 现象和结论

打印结果和原来相比:

  1. 自动补全原本缺失的li标签
  2. 自动补全html等标签
<html><body><div> <ul> 
<li class="item-1"><a href="link1.html">first item</a></li> 
<li class="item-1"><a href="link2.html">second item</a></li> 
<li class="item-inactive"><a href="link3.html">third item</a></li> 
<li class="item-1"><a href="link4.html">fourth item</a></li> 
<li class="item-0"><a href="link5.html">fifth item</a> 
</li></ul> </div> </body></html>

结论

  • lxml.etree.HTML(html_str)可以自动补全标签

  • lxml.etree.tostring函数可以将转换为Element对象再转换回html字符串

  • 爬虫如果使用lxml来提取数据,应该以lxml.etree.tostring的返回结果作为提取数据的依据

百度贴吧
import requests
from lxml import etree


class Tieba(object):
    def __init__(self, name):
        # url
        self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw={}&fr=search'.format(name)
        # hearders
        self.headers = {
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'
        }


    # 发送请求获取响应
    def get_data(self, url):
        # 发送请求获取相应
        response = requests.get(url, headers = self.headers)
        return response.content


    # 解析数据
    def parse_data(self, data):
        # 创建element 对象
        html = etree.HTML(data)
        el_list = html.xpath("//li/div/div[2]/div[1]/div[1]/a")
        print(len(el_list))

    def run(self):
        # 1.url,hearders
        # 2.发送请求获取相应
        data = self.get_data(self.url)
        # 3.从响应中提取数据(数据和翻页用的url,翻页终结条件是判断是否提取到下一页)
        self.parse_data(data)


if __name__ == '__main__':
    tieba = Tieba('万花谷')
    tieba.run()

在这里插入图片描述
通过查找response中源码,发现代码被引擎注释

在这里插入图片描述
更换用户请求头user-agent
http://tools.jb51.net/table/useragent
在这里插入图片描述

import requests
from lxml import etree


class Tieba(object):
    def __init__(self, name):
        # url
        self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw={}&fr=search'.format(name)
        # hearders
        self.headers = {
            # 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'
            'User-Agent':'User-Agent,Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'
        }


    # 发送请求获取响应
    def get_data(self, url):
        # 发送请求获取相应
        response = requests.get(url, headers = self.headers)
        return response.content


    # 解析数据
    def parse_data(self, data):
        # 创建element 对象
        html = etree.HTML(data)
        el_list = html.xpath("//li/div/div[2]/div[1]/div[1]/a")
        print(len(el_list))

    def run(self):
        # 1.url,hearders
        # 2.发送请求获取相应
        data = self.get_data(self.url)
        # 3.从响应中提取数据(数据和翻页用的url,翻页终结条件是判断是否提取到下一页)
        self.parse_data(data)


if __name__ == '__main__':
    tieba = Tieba('万花谷')
    tieba.run()

在这里插入图片描述
或者,在获取response时候进行decode(),将注释符号 <!— 和—>进行替换

import requests
from lxml import etree


class Tieba(object):
    def __init__(self, name):
        # url
        self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw={}&fr=search'.format(name)
        # hearders
        self.headers = {
             'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'
            #'User-Agent':'User-Agent,Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'
        }


    # 发送请求获取响应
    def get_data(self, url):
        # 发送请求获取相应
        response = requests.get(url, headers = self.headers)
        return response.content


    # 解析数据
    def parse_data(self, data):
    	# 替换注释符号
        data = data.decode().replace('<!--','').replace('-->','')
        # 创建element 对象
        html = etree.HTML(data)
        el_list = html.xpath("//li/div/div[2]/div[1]/div[1]/a")
        print(len(el_list))

    def run(self):
        # 1.url,hearders
        # 2.发送请求获取相应
        data = self.get_data(self.url)
        # 3.从响应中提取数据(数据和翻页用的url,翻页终结条件是判断是否提取到下一页)
        self.parse_data(data)


if __name__ == '__main__':
    tieba = Tieba('万花谷')
    tieba.run() # 50 
import requests
from lxml import etree


class Tieba(object):
    def __init__(self, name):
        # url
        self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw={}&fr=search'.format(name)
        # hearders
        self.headers = {
            # 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'
            'User-Agent':'User-Agent,Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'
        }


    # 发送请求获取响应
    def get_data(self, url):
        # 发送请求获取相应
        response = requests.get(url, headers = self.headers)
        return response.content


    # 解析数据
    def parse_data(self, data):
        # 创建element 对象
        html = etree.HTML(data)
        el_list = html.xpath("//li/div/div[2]/div[1]/div[1]/a")
        print(len(el_list))

    def run(self):
        # 1.url,hearders
        # 2.发送请求获取相应
        data = self.get_data(self.url)
        # 3.从响应中提取数据(数据和翻页用的url,翻页终结条件是判断是否提取到下一页)
        self.parse_data(data)


if __name__ == '__main__':
    tieba = Tieba('万花谷')
    tieba.run()

提取数据
tips: 在获取下一页href时候,尽量不要使用索引
在这里插入图片描述
推荐
在这里插入图片描述
完整代码(若爬取不了是由于百度贴吧验证码问题)

import requests
from lxml import etree


class Tieba(object):
    def __init__(self, name):
        # url
        self.url = 'https://tieba.baidu.com/f?kw={}&ie=utf-8'.format(name)
        # hearders
        self.headers = {
             'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'
            #'User-Agent':'User-Agent,Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'
        }


    # 发送请求获取响应
    def get_data(self, url):
        # 发送请求获取相应
        response = requests.get(url, headers = self.headers)
        return response.content


    # 解析数据
    def parse_data(self, data):
        data = data.decode().replace('<!--','').replace('-->','')
        # 创建element 对象
        html = etree.HTML(data)
        el_list = html.xpath("//li/div/div[2]/div[1]/div[1]/a")
        # print(len(el_list))
        # 存储每页的内容
        data_list = []
        for el in el_list:
            temp = {}
            temp['title'] = el.xpath('./text()')[0]
            # el.xpath('./@href')[0] -> '/p/7371993493'
            temp['link'] = 'tieba.baidu.com' + el.xpath('./@href')[0]
            data_list.append(temp)
        # print(data_list)
        # 获取下一页url
        # 找翻页url的时候尽量不要用索引 如//*[@id="frs_list_pager"]/a[10] 改为//a[@class = 'next pagination-item']/@href
        # 最后一页时候,无‘下一页’,因此try
        # print(html.xpath("//*[@id='frs_list_pager']/a[contains(text(), '下一')]/@href"))

        # print('https:' + html.xpath("//*[@id='frs_list_pager']/a[contains(text(), '下一')]/@href")[0])
        try:
            next_url = 'https:' + html.xpath("//*[@id='frs_list_pager']/a[contains(text(), '下一')]/@href")[0]
            # next_url = 'https:' + html.xpath("//a[contains(text(), '下一页>')]/@href")[0]
        except:
            next_url = None
        return data_list, next_url


    def save_data(self, data_list):
        with open('./tieba.txt', 'a', encoding = 'utf-8') as f:
            for data in data_list:
                # print(data) # {'title': '对比后的我终于下了手', 'link': 'tieba.baidu.com/p/7363785824'}
                f.write('title:' + data['title'] + '\t' + 'link:' + data['link'] + '\n')


    def run(self):
        # 1.url,hearders
        next_url = self.url
        while True:
            # 2.发送请求获取相应
            data = self.get_data(next_url)
            # 3.从响应中提取数据(数据和翻页用的url,翻页终结条件是判断是否提取到下一页)
            data_list, next_url = self.parse_data(data)
            # 5.保存数据
            self.save_data(data_list)
            # 4.判断是否终结
            if next_url == None:
                break


if __name__ == '__main__':
    tieba = Tieba('LOL')
    tieba.run()

selenium的介绍

1. selenium运行效果展示

Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接调用浏览器,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器),可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏等。可以使用selenium很容易完成之前编写的爬虫

1.1 chrome浏览器的运行效果

在下载好chromedriver以及安装好selenium模块后,执行下列代码并观察运行的过程

from selenium import webdriver 

# 如果driver没有添加到了环境变量,则需要将driver的绝对路径赋值给executable_path参数
# driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver')

# 如果driver添加了环境变量则不需要设置executable_path
driver = webdriver.Chrome()

# 向一个url发起请求
driver.get("http://www.baidu.cn/")

# 把网页保存为图片,69版本以上的谷歌浏览器将无法使用截图功能
# driver.save_screenshot("baidu.png")

print(driver.title) # 打印页面的标题

# 退出模拟浏览器
driver.quit() # 一定要退出!不退出会有残留进程!
1.2 phantomjs无界面浏览器的运行效果

PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript。下载地址:http://phantomjs.org/download.html

from selenium import webdriver 

# 指定driver的绝对路径
driver = webdriver.PhantomJS(executable_path='/home/worker/Desktop/driver/phantomjs') 
# driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver')

# 向一个url发起请求
driver.get("http://www.baidu.cn/")

# 把网页保存为图片
driver.save_screenshot("baidu.png")

# 退出模拟浏览器
driver.quit() # 一定要退出!不退出会有残留进程!

遇到报错selenium.common.exceptions.WebDriverException: Message: 'phantomjs-2.1.1-windows' executable may have wrong permissions.
解决办法
在python安装目录中copy phantomjs.exe或者在python脚本下copy phantomjs.exe(参考后面chromedriver)
在这里插入图片描述
运行成功
在这里插入图片描述

1.3 观察运行效果
  • python代码能够自动的调用谷歌浏览或phantomjs无界面浏览器,控制其自动访问网站

    在这里插入图片描述

1.4 无头浏览器与有头浏览器的使用场景
  • 通常在开发过程中我们需要查看运行过程中的各种情况所以通常使用有头浏览器
  • 在项目完成进行部署的时候,通常平台采用的系统都是服务器版的操作系统,服务器版的操作系统必须使用无头浏览器才能正常运行

2. selenium的作用和工作原理

利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)

在这里插入图片描述

  • webdriver本质是一个web-server,对外提供webapi,其中封装了浏览器的各种功能
  • 不同的浏览器使用各自不同的webdriver

3. selenium的安装以及简单使用

以谷歌浏览器的chromedriver为例

3.1 在python虚拟环境中安装selenium模块

pip/pip3 install selenium

3.2 下载版本符合的webdriver

以chrome谷歌浏览器为例

  1. 查看谷歌浏览器的版本

    在这里插入图片描述

    在这里插入图片描述

  2. 访问https://npm.taobao.org/mirrors/chromedriver,点击进入不同版本的chromedriver下载页面

    在这里插入图片描述

  3. 点击notes.txt进入版本说明页面

    在这里插入图片描述

  4. 查看chrome和chromedriver匹配的版本

    在这里插入图片描述

  5. 根据操作系统下载正确版本的chromedriver

    在这里插入图片描述

  6. 解压压缩包后获取python代码可以调用的谷歌浏览器的webdriver可执行文件

    • windows为chromedriver.exe

    • linux和macos为chromedriver

  7. chromedriver环境的配置

测试代码

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
time.sleep(3)
driver.quit()

配置成功
在这里插入图片描述

  • linux/mac环境下,将 chromedriver 所在的目录设置到系统的PATH环境值中

4. selenium的简单使用

接下来我们就通过代码来模拟百度搜索

import time
from selenium import webdriver

# 通过指定chromedriver的路径来实例化driver对象,chromedriver放在当前目录。
# driver = webdriver.Chrome(executable_path='./chromedriver')
# chromedriver已经添加环境变量
driver = webdriver.Chrome()

# 控制浏览器访问url地址
driver.get("https://www.baidu.com/")

# 在百度搜索框中搜索'python'
driver.find_element_by_id('kw').send_keys('python')
# 点击'百度搜索'
driver.find_element_by_id('su').click()

time.sleep(6)
# 退出浏览器
driver.quit()
  • webdriver.Chrome(executable_path='./chromedriver')中executable参数指定的是下载好的chromedriver文件的路径
  • driver.find_element_by_id('kw').send_keys('python')定位id属性值是’kw’的标签,并向其中输入字符串’python’
  • driver.find_element_by_id('su').click()定位id属性值是su的标签,并点击
    • click函数作用是:触发标签的js的click事件

selenium提取数据

1. driver对象的常用属性和方法

在使用selenium过程中,实例化driver对象后,driver对象有一些常用的属性和方法

  1. driver.page_source 当前标签页浏览器渲染之后的网页源代码
  2. driver.current_url 当前标签页的url
  3. driver.close() 关闭当前标签页,如果只有一个标签页则关闭整个浏览器
  4. driver.quit() 关闭浏览器
  5. driver.forward() 页面前进
  6. driver.back() 页面后退
  7. driver.screen_shot(img_name) 页面截图,常用于验证是否运行或者验证码截图

2. driver对象定位标签元素获取标签对象的方法

在selenium中可以通过多种方式来定位标签,返回标签元素对象

find_element_by_id 						(返回一个元素)
find_element(s)_by_class_name 			(根据类名获取元素列表)
find_element(s)_by_name 				(根据标签的name属性值返回包含标签对象元素的列表)
find_element(s)_by_xpath 				(返回一个包含元素的列表)
find_element(s)_by_css_selector 		(根据css选择器来获取元素列表)

#  by_link_text 全部文本
# by_partial_link_tex 包含某个文本
find_element(s)_by_link_text 			(根据连接文本获取元素列表)
find_element(s)_by_partial_link_text 	(根据链接包含的文本获取元素列表)

# 适用于目标元素在当前html中是唯一标签的时候或是众多定位出来的标签中的第一个的时候才可使用 
find_element(s)_by_tag_name 			(根据标签名获取元素列表)
  • 注意:

    • find_element和find_elements的区别:
      • 多了个s就返回列表,没有s就返回匹配到的第一个标签对象(定位到就返回一个对象,定位不到则报错的)
      • find_element匹配不到就抛出异常,find_elements匹配不到就 返回 空列表
    • by_link_text和by_partial_link_tex的区别:全部文本包含某个文本
    	# 通过链接文本进行元素定位
      driver.find_element_by_link_text('百度首页').click()
    

    在这里插入图片描述

    • 以上函数的使用方法
      • driver.find_element_by_id('id_str')
      • element_list = driver.find_elements_by_xpath('/html/body/div[6]/div[2]/ul/li')

3. 标签对象提取文本内容和属性值

find_element仅仅能够获取元素,不能够直接获取其中的数据,如果需要获取数据需要使用以下方法

  • 对元素执行点击操作element.click()

    • 对定位到的标签对象进行点击操作
  • 向输入框输入数据element.send_keys(data)

    • 对定位到的标签对象输入数据
  • 对输入框进行清空操作element.clear()

    • 对定位到的标签对象进行清空操作
  • 获取文本element.text

    • 通过定位获取的标签对象的text属性,获取文本内容
  • 获取属性值element.get_attribute("属性名")

    • 通过定位获取的标签对象的get_attribute函数,传入属性名,来获取属性的值

代码实现,如下:

from selenium import webdriver

url = 'https://cd.58.com/chuzu/?utm_source=market'
driver = webdriver.Chrome()
driver.get(url)

elem_list = driver.find_elements_by_xpath('/html/body/div[6]/div[2]/ul/li/div[2]/h2/a')
# print(elem_list)
for el in elem_list:
  print(el)
  print(el.text, el.get_attribute('href'))
driver.quit()

selenium的其它使用方法

1. selenium标签页的切换

当selenium控制浏览器打开多个标签页时,如何控制浏览器在不同的标签页中进行切换呢?需要我们做以下两步:

  • 获取所有标签页的窗口句柄

  • 利用窗口句柄字切换到句柄指向的标签页

  • 具体的方法

    # 1. 获取当前所有的标签页的句柄构成的列表
    current_windows = driver.window_handles
    
    # 2. 根据标签页句柄列表索引下标进行切换
    driver.switch_to.window(current_windows[0])
    
  • 参考代码示例:

    import time
    from selenium import webdriver
    
    driver = webdriver.Chrome()
    driver.get("https://www.baidu.com/")
    
    time.sleep(1)
    driver.find_element_by_id('kw').send_keys('python')
    time.sleep(1)
    driver.find_element_by_id('su').click()
    time.sleep(1)
    
    # 通过执行js来新开一个标签页
    js = 'window.open("https://www.sogou.com");'
    driver.execute_script(js)
    time.sleep(1)
    
    # 1. 获取当前所有的窗口
    windows = driver.window_handles
    
    time.sleep(2)
    # 2. 根据窗口索引进行切换
    driver.switch_to.window(windows[0])
    time.sleep(2)
    driver.switch_to.window(windows[1])
    
    time.sleep(6)
    driver.quit()
    

2. switch_to切换frame标签

iframe是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是driver.switch_to.frame(frame_element)。接下来通过qq邮箱模拟登陆来学习这个知识点

在这里插入图片描述
在这里插入图片描述

  • 参考代码:

    import time
    from selenium import webdriver
    
    driver = webdriver.Chrome()
    
    url = 'https://mail.qq.com/cgi-bin/loginpage'
    driver.get(url)
    time.sleep(2)
    
    login_frame = driver.find_element_by_id('login_frame') # 根据id定位 frame元素
    driver.switch_to.frame(login_frame) # 转向到该frame中
    
    driver.find_element_by_xpath('//*[@id="u"]').send_keys('1596930226@qq.com')
    time.sleep(2)
    
    driver.find_element_by_xpath('//*[@id="p"]').send_keys('hahamimashicuode')
    time.sleep(2)
    
    driver.find_element_by_xpath('//*[@id="login_button"]').click()
    time.sleep(2)
    
    """操作frame外边的元素需要切换出去"""
    windows = driver.window_handles
    driver.switch_to.window(windows[0])
    
    content = driver.find_element_by_class_name('login_pictures_title').text
    print(content)
    
    driver.quit()
    
  • 总结:

    • 切换到定位的frame标签嵌套的页面中

      • driver.switch_to.frame(通过find_element_by函数定位的frame、iframe标签对象)
      from selenium import webdriver
      import time
      
      
      url = 'https://qzone.qq.com/'
      driver = webdriver.Chrome()
      driver.get(url)
      
      # 登录为iframe,需要切换进相应frame中
      # 可以使用相应frame的id进行切换
      driver.switch_to.frame('login_frame')
      
      """
      或者
      el_frame = driver.find_element_by_xpath('//*[@id="login_div"]')
      driver.switch_to.frame(el_frame)
      """
      
      # 点击使用账号密码登录
      driver.find_element_by_id('switcher_plogin').click()
      
      # 填写账号密码
      driver.find_element_by_id('u').send_keys('qq')
      driver.find_element_by_id('p').send_keys('mima ')
      # 点击登录
      driver.find_element_by_id('login_button').click()
      
    • 利用切换标签页的方式切出frame标签

      • windows = driver.window_handles
        driver.switch_to.window(windows[0])
        

3. selenium对cookie的处理

selenium能够帮助我们处理页面中的cookie,比如获取、删除,接下来我们就学习这部分知识

3.1 获取cookie

driver.get_cookies()返回列表,其中包含的是完整的cookie信息!不光有name、value,还有domain等cookie其他维度的信息。所以如果想要把获取的cookie信息和requests模块配合使用的话,需要转换为name、value作为键值对的cookie字典

# 获取当前标签页的全部cookie信息
print(driver.get_cookies())
# 把cookie转化为字典
cookies_dict = {cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()}
3.2 删除cookie
#删除一条cookie
driver.delete_cookie("CookieName")

# 删除所有的cookie
driver.delete_all_cookies()

4. selenium控制浏览器执行js代码

selenium可以让浏览器执行指定的js代码(普遍应用于含有触发式请求中:如下拉加载内容 ),运行下列代码查看运行效果

import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.get("http://www.itcast.cn/")
time.sleep(1)

# 由于要点击内容不在当前窗口内,需要控制js将窗口下拉
# js = 'scrollTo(x,y)' # x, y 为窗口水平和垂直移动距离 (0,0)为左上角
# js = 'scrollTo(0, 100)'
js = 'window.scrollTo(0,document.body.scrollHeight)' # js语句
driver.execute_script(js) # 执行js的方法

time.sleep(5)
driver.quit()
  • 执行js的方法:driver.execute_script(js)

5. 页面等待

页面在加载的过程中需要花费时间等待网站服务器的响应,在这个过程中标签元素有可能还没有加载出来,是不可见的,如何处理这种情况呢?

  1. 页面等待分类
  2. 强制等待介绍
  3. 显式等待介绍
  4. 隐式等待介绍
  5. 手动实现页面等待
5.1 页面等待的分类

首先我们就来了解以下selenium页面等待的分类

  1. 强制等待
  2. 隐式等待
  3. 显式等待
5.2 强制等待(了解)
  • 其实就是time.sleep()
  • 缺点时不智能,设置的时间太短,元素还没有加载出来;设置的时间太长,则会浪费时间
5.3 隐式等待
  • 隐式等待针对的是元素定位,隐式等待设置了一个时间,在一段时间内判断元素是否定位成功,如果完成了,就进行下一步(只需设置一次,后续都会进行等待

  • 在设置的时间内没有定位成功,则会报超时加载

  • 示例代码

    from selenium import webdriver
    
    driver = webdriver.Chrome()  
    
    # 设置之后的所有元素定位操作最大等待时间为10S,10S内会定期进行元素定位,超过设置时间后,将报错
    driver.implicitly_wait(10) # 隐式等待,最长等10秒  
    
    driver.get('https://www.baidu.com')
    
    driver.find_element_by_xpath()
    
    
5.4 显式等待(了解)
  • 每经过多少秒就查看一次等待条件是否达成,如果达成就停止等待,继续执行后续代码

  • 如果没有达成就继续等待直到超过规定的时间后,报超时异常(显式等待是明确等待某一个元素,而隐式是针对任何存在的元素定位操作进行等待,显式等待用于软件测试中

  • 示例代码

    from selenium import webdriver  
    from selenium.webdriver.support.wait import WebDriverWait  
    from selenium.webdriver.support import expected_conditions as EC  
    from selenium.webdriver.common.by import By 
    
    driver = webdriver.Chrome()
    
    driver.get('https://www.baidu.com')
    
    # 显式等待
    WebDriverWait(driver, 20, 0.5).until(
        EC.presence_of_element_located((By.LINK_TEXT, '好123')))  
    # 参数20表示最长等待20秒
    # 参数0.5表示0.5秒检查一次规定的标签是否存在
    # EC.presence_of_element_located((By.LINK_TEXT, '好123')) 表示通过链接文本内容定位标签
    # 每0.5秒一次检查,通过链接文本内容定位标签是否存在,如果存在就向下继续执行;如果不存在,直到20秒上限就抛出异常
    
    print(driver.find_element_by_link_text('好123').get_attribute('href'))
    driver.quit() 
    
5.5 手动实现页面等待

在了解了隐式等待和显式等待以及强制等待后,并没有一种通用的方法来解决页面等待的问题,比如“页面需要滑动才能触发ajax异步加载”的场景,那么接下来以淘宝网首页为例,手动实现页面等待(隐式等待>强制等待>显式等待)

  • 原理:
    • 利用强制等待和显式等待的思路来手动实现
    • 不停的判断或有次数限制的判断某一个标签对象是否加载完毕(是否存在)
  • 实现代码如下:
import time
from selenium import webdriver
driver = webdriver.Chrome('/home/worker/Desktop/driver/chromedriver')

driver.get('https://www.taobao.com/')
time.sleep(1)

# i = 0
# while True:
for i in range(10):
    i += 1
    try:
        time.sleep(3)
        element = driver.find_element_by_xpath('//div[@class="shop-inner"]/h3[1]/a')
        print(element.get_attribute('href'))
        break
    except:
        js = 'window.scrollTo(0, {})'.format(i*500) # js语句
        driver.execute_script(js) # 执行js的方法
driver.quit()

6. selenium开启无界面模式

绝大多数服务器是没有界面的,selenium控制谷歌浏览器也是存在无界面模式的,这一小节来学习如何开启无界面模式(又称之为无头模式(部署时使用无头))

  • 开启无界面模式的方法
    • 实例化配置对象
      • options = webdriver.ChromeOptions()
    • 配置对象添加开启无界面模式的命令
      • options.add_argument("--headless")
    • 配置对象添加禁用gpu的命令
      • options.add_argument("--disable-gpu")
    • 实例化带有配置对象的driver对象
      • driver = webdriver.Chrome(chrome_options=options)
  • 注意:macos中chrome浏览器59+版本,Linux中57+版本才能使用无界面模式
  • 参考代码如下:
from selenium import webdriver

options = webdriver.ChromeOptions() # 创建一个配置对象
options.add_argument("--headless") # 开启无界面模式
options.add_argument("--disable-gpu") # 禁用gpu

# options.set_headles() # 无界面模式的另外一种开启方式
driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象

driver.get('http://www.baidu.com')
print(driver.title)
driver.quit()

7. selenium使用代理ip

selenium控制浏览器也是可以使用代理ip的!

  • 使用代理ip的方法

    • 实例化配置对象
      • options = webdriver.ChromeOptions()
    • 配置对象添加使用代理ip的命令
      • options.add_argument('--proxy-server=http://202.20.16.82:9527')
    • 实例化带有配置对象的driver对象
      • driver = webdriver.Chrome('./chromedriver', chrome_options=options)
  • 参考代码如下:

    from selenium import webdriver
    
    options = webdriver.ChromeOptions() # 创建一个配置对象
    options.add_argument('--proxy-server=http://202.20.16.82:9527') # 使用代理ip
    
    driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象
    
    driver.get('http://www.baidu.com')
    print(driver.title)
    driver.quit()
    

8. selenium替换user-agent

selenium控制谷歌浏览器时,User-Agent默认是谷歌浏览器的,这一小节我们就来学习使用不同的User-Agent

  • 替换user-agent的方法

    • 实例化配置对象
      • options = webdriver.ChromeOptions()
    • 配置对象添加替换UA的命令
      • options.add_argument('--user-agent=Mozilla/5.0 HAHA')
    • 实例化带有配置对象的driver对象
      • driver = webdriver.Chrome('./chromedriver', chrome_options=options)
  • 参考代码如下:

    from selenium import webdriver
    
    options = webdriver.ChromeOptions() # 创建一个配置对象
    options.add_argument('--user-agent=Mozilla/5.0 HAHA') # 替换User-Agent
    
    driver = webdriver.Chrome('./chromedriver', chrome_options=options)
    
    driver.get('http://www.baidu.com')
    print(driver.title)
    driver.quit()
    

斗鱼直播信息爬取

from selenium import webdriver
import time


class Douyu(object):

    def __init__(self):
        self.url = 'https://www.douyu.com/directory/all'
        self.driver = webdriver.Chrome()


    def parse_data(self):
        # 存档数据
        data_list = []
        # 获取房间列表
        room_list = self.driver.find_elements_by_xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li')
        js = 'scrollTo(document.body.scrollWidth, document.body.scrollHeight)'
        self.driver.execute_script(js)
        # 遍历房间列表,从每一个房间节点中提取数据
        for i in range(len(room_list)):
            # 当爬取时,页面会刷新,导致room_list定位失败,需要重新获取
            # 以免 stale element reference: element is not attached to the page document
            room_list = self.driver.find_elements_by_xpath('//*[@id="listAll"]/section[2]/div[2]/ul/li')
            temp = {}
            temp['title'] = room_list[i].find_element_by_xpath('./div/a/div[2]/div[1]/h3').text
            temp['type'] = room_list[i].find_element_by_xpath('./div/a/div[2]/div[1]/span').text
            temp['num'] = room_list[i].find_element_by_xpath('./div/a/div[2]/div[2]/span').text
            temp['owner'] = room_list[i].find_element_by_xpath('./div/a/div[2]/div[2]/h2').text
            data_list.append(temp)
            # 同room_list 以免 stale element reference: element is not attached to the page document
            # print(temp)
            time.sleep(1)
        print('该页爬取完毕!')
        return data_list

    def save_data(self, i, data_list):
        with open('./douyu/douyu'+str(i)+'.txt', 'w', encoding='utf-8') as f:
            for data in data_list:
                f.write('title:'+data['title']+'\t'+'type:'+data['type']+'\t'+'num'+data['num']+'\t'+'owner'+data['owner']+'\n')


    def run(self):
        # url
        # driver
        # get
        self.driver.get(self.url)
        i = 1
        while True:
            # parse
            data_list = self.parse_data()
            # save
            self.save_data(i, data_list)
            # next 获取下一页
            try:
                # 定位到下一页则点击
                el_next = self.driver.find_element_by_xpath('//*[contains(text(), "下一页")]')
                
                # 点击翻页前,需要下拉刷新操作
                self.driver.execute_script('scrollTo(document.body.scrollWidth,document.body.scrollHeight)')
                el_next.click()
                # 以免 stale element reference: element is not attached to the page document
                time.sleep(1)
            except:
                # 定位不到则退出
                break
            i += 1



if __name__ == '__main__':
    douyu = Douyu()
    douyu.run()

在这里插入图片描述

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值