基本能力(必看)
-
会使用浏览器上网,建议浏览器Google , Edge。(不建议国内浏览器)
-
会检查网页代码, 抓包分析, js逆向等。
-
一定的python,HTML基础
-
小白请使用参考视屏辅助学习
-
本人也是小白一枚
http复习
http与https的概念与区别
https比http更安全但性能更低
常见的请求头与响应头
请求头:
host:域名
Connection:长连接
Upgrade-Insecure-Requests:升级为https协议
* User-Agent:用户代理,提供系统浏览器信息
* Referer:页面跳转处(从哪来),防盗链
* Cookie:状态保持
响应头:
set-cookie:设置HTTP cookie
注意:以上仅是常用请求头,其余请参见: 请求头与响应头
状态码
分类:
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
常见状态码:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
详情参见:状态码
注意:所有状态码都不可信,一切以从network中抓包分析为准(防反爬)
浏览器的请求过程
浏览器:发送请求,进行渲染
爬虫:只发送请求,不渲染
渲染流程:html静态文件 --> js/ajax请求 --> css/font/图片
注意:以上渲染的流程也是抓包分析的流程
requests模块
简介
-
作用:发送请求获取响应,替代模块urllib,但requests模块更简洁明了。
-
发送get请求
import requests
# 目标url
url = 'https://www.baidu.com'
# 向目标url发送get请求
response = requests.get(url)
# 打印响应内容
print(response.text)
response响应对象
-
response.text是requests模块按照chardet模块推测出的编码字符集进行解码的结果。
-
因此可以手动设置解码格式
# 设置解码格式
response.encoding = 'utf-8'
# 打印响应内容
print(response.text)
- 网络传输的字符串都是bytes类型的,所以可以对字节流进行解码
# 或者对字节流进行解码
print(response.content.decode('utf-8'))
总结:
-
response.text:类型是str,已经做了相应解码。
-
response.content:类型是bytes,没有指定解码类型,可以自由设置解码类型。
-
常见编码字符集:utf-8, gbk, gb2312, ascii, iso-8859-1
response响应对象的其他方法
# 响应的url,有时响应的url与请求的url并不一致
print(response.url)
# 响应状态码
print(response.status_code)
# 响应对应的请求头
print(response.request.headers)
# 响应头
print(response.headers)
# 响应对应的请求的cookie,返回cookieJar类型
print(response.request._cookies)
# 响应的cookie(经过了set-cookie动作后)
print(response.cookies)
# 自动将json字符串类型的响应内容转为python对象
print(response.json())
# 返回结果
# https://www.baidu.com/
# 200
# {'User-Agent': 'python-requests/2.26.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
# {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'keep-alive', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Fri, 08 Oct 2021 03:54:39 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:23:55 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Transfer-Encoding': 'chunked'}
# <RequestsCookieJar[]>
# <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
requests模块发送get请求(带参数)
发送带请求头的参数
import requests
# 目标url
url = 'https://www.baidu.com'
# 请求头, 模拟浏览器发送请求
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}
# 向目标url发送get请求
response = requests.get(url, headers=headers)
print(response.content.decode())
发送带参数的请求
- 直接对含有参数的url发起请求
# 直接对含有参数的url发起请求
import requests
url = "https://www.baidu.com/s?wd=python"
response = requests.get(url, headers=headers)
print(response.content.decode())
- 建立参数字典
# 建立参数字典
import requests
url = "https://www.baidu.com/s?"
# 请求头, 模拟浏览器发送请求
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application"
"/signed-exchange;v=b3;q=0.9",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}
params = {
"wd": "python"
}
response = requests.get(url, headers=headers, params=params)
print(response.content.decode())
注意:如果出现百度安全验证,则在请求头中加入"Accept",“Accept-Language”
cookie参数
网站经常利用请求头中的cookie字段来做用户访问状态的保持,因此我们可以在headers参数中添加cookie,来模拟普通用户的请求
import requests
url = 'https://www.acwing.com/problem/'
# 请求头, 模拟浏览器发送请求
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67",
"Cookie": "csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG; sessionid=d3w2dub0yas41kevn"
"ihdm6qoib28q939"
}
response = requests.get(url, headers=headers)
print(response.content.decode())
# 出现个人信息:说明模拟登陆成功(大约在390-400行出现)
# <strong id="id_user_username">陆依依</strong>
注意:
-
上述cookie值中包含本人个人信息,请慎用!!!
-
cookie一般有过期时间,需要重新获取
-
也可以在get请求中加入cookies参数,如下:
import requests
# 使用cookie参数
url = 'https://www.acwing.com/problem/'
# 请求头, 模拟浏览器发送请求
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}
cookies = "csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG; sessionid=d3w2dub0yas41kevn" \
"ihdm6qoib28q939"
# 加工cookies字典
cookies = {cookie.split("=")[0]: cookie.split("=")[-1] for cookie in cookies.split(";")}
response = requests.get(url, headers=headers, cookies=cookies)
print(response.content.decode())
cookieJar对象转换为cookie字典
response.cookies是一个cookieJar对象,包含了对方服务器设置在本地的cookie
# 获取的cookies属性
print(response.cookies)
# 转为字典
cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)
print(cookies_dict)
# 转为cookieJar对象
cookies_jar = requests.utils.cookiejar_from_dict(cookies_dict)
print(cookies_jar)
# # 运行结果:
# <RequestsCookieJar[<Cookie csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG for www.acwing.com/>]>
# {'csrftoken': 'b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG'}
# <RequestsCookieJar[<Cookie csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG for />]>
超时参数timeout的使用
在网络请求中,可能会遇到网络延迟,造成项目效率降低,timeout可以使程序在规定时间内必须返回结果,否则报错!(抛出异常)
import requests
url = ''
# 3秒内没有响应会抛出异常
requests.get(url, timeout=3)
# 运行结果:
# requests.exceptions.MissingSchema: Invalid URL '': No schema supplied. Perhaps you meant http://?
代理proxy
- 理解使用代理的过程
1、代理IP是一个IP,指向的是一个代理服务器
2、代理服务器能够帮我们找到目标服务器并转发请求
- 正向代理和反向代理
1、正向代理:为浏览器或客户端(发送请求的一方)转发请求,如VPN
2.反向代理:为最终处理请求的服务器转发请求,如nginx
3、正向代理知道目标服务器,而反向代理不知道
4、proxy参数指定的代理IP指向的是正向代理服务器
- 代理的分类
根据IP划分
透明代理:使用了透明代理去访问目标服务器,对方服务器会知道你正在使用代理,并且能识别你本地真实ip地址
透明代理访问目标服务器的所带的HTTP头信息如下:
REMOTE_ADDR = 代理服务器IP
HTTP_VIA = 代理服务器IP
HTTP_X_FORWARDED_FOR = 你的真实IP
透明代理无法隐藏真实ip地址,因此无法突破目标服务器的限制。
匿名代理:使用了匿名代理去访问目标服务器,对方服务器会知道你正在使用代理,但是无法识别你的真实IP
匿名代理访问目标服务器所带的HTTP头信息如下:
REMOTE_ADDR = 代理服务器IP
HTTP_VIA = 代理服务器IP
HTTP_X_FORWARDED_FOR = 代理服务器IP
高匿名代理:使用高匿名代理目标服务器不仅无法识别你的真实ip,还无法识别你使用了代理。
高匿名代理访问对方服务器所带的HTTP头信息如下:
REMOTE_ADDR = 代理服务器IP
HTTP_VIA 不显示
HTTP_X_FORWARDED_FOR 不显示
高匿名代理很好的隐藏本地ip,使用伪装的ip去访问目标网站,从而突破网站的ip限制。
所以在选择代理ip的时候一定要选择像万变ip这样的代理,每日上百万动态高匿名ip。
只有高匿名的ip才能突破各种ip限制。
根据协议划分
HTTP代理:能够代理客户机的HTTP访问,主要是代理浏览器访问网页,它的端口一般为80、8080、3128等。
FTP代理:能够代理客户机上的FTP软件访问FTP服务器,它的端口一般为21、2121。
RTSP代理:代理客户机上的Realplayer访问Real流媒体服务器的代理,其端口一般为554。
POP3代理:代理客户机上的邮件软件用POP3方式收发邮件,端口一般为110。
SOCKS代理:SOCKS代理与其他类型的代理不同,它只是简单地传递数据包,而并不关心是何种应用协议,所以SOCKS代理服务器比其他类型的代理服务器速度要快得多。
SOCKS代理又分为SOCKS4和SOCKS5,二者不同的是SOCKS4代理只支持TCP协议(即传输控制协议),而SOCKS5代理则既支持TCP协议又支持UDP协议(即用户数据包协议),还支持各种身份验证机制、服务器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但SOCKS5能够做到的SOCKS则不一定能做到,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。
- 使用代理
import requests
# 目标url
url = 'https://www.baidu.com'
proxies = {
"http": "http://112.6.117.178:8085"
}
# 请求头, 模拟浏览器发送请求
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}
# 向目标url发送get请求
response = requests.get(url, headers=headers, proxies=proxies)
print(response.content.decode())
注意:
-
代理IP尽量使用http类型,并加上headers头,否则容易报错
CA证书
有些网站没有CA证书,访问会失败,需要加一个参数:verify=False来忽略CA认证
response = requests.get(url, verify=False)
requests模块发送post请求
- post更安全且没有数据长度限制
- post请求相对于get请求多了一个data参数,data是一个字典存放表单参数
- 下面以md5加密为例,演示post请求的用法(适当添加面向对象的思想)
# json.loads()可以将str类型转为json对象
import json
import requests
class Md5(object):
def __init__(self, message):
self.url = 'https://www.matools.com/md5'
self.data = {
"str": message
}
def run(self):
response = requests.post(self.url, data=self.data)
print(json.loads(response.content.decode())["upper32"])
print(json.loads(response.content.decode())["lower32"])
print(json.loads(response.content.decode())["upper16"])
print(json.loads(response.content.decode())["lower16"])
if __name__ == '__main__':
message = "网络空间安全"
md5 = Md5(message)
md5.run()
# 运行结果:
# 32位大写:D58389AB3D32FD387335B2B82AC8D0DB
# 32位小写:d58389ab3d32fd387335b2b82ac8d0db
# 16位大写:3D32FD387335B2B8
# 16位小写:3d32fd387335b2b8
总结:post中data参数数据来源:
1. 固定值 抓包比较不变值
2. 输入值 自己手动输入
3. 预设值 需要提前在html文件中获取,或者向指定url发送请求
4. 在客户端产生 模拟js,产生数据
补充知识:时间戳
定义:时间戳是指格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数。
获取方式: 先 import time 然后 time.time()
例子:1633758382.6187418
利用requests.session进行状态保持
-
requests中的session类能够自动处理发送请求获取响应过程中产生的cookie,进而达到状态保持的目的。
-
session实例在请求一个网站后,对方服务器设置在本地的cookie会保持在session中,下一次在使用session请求对方服务器时,会带上上一次的cookie。
-
一般用于模拟登录场景。
# 学习通中的密码仅仅采用base64编码,并未加密
# 登录过程中,还用到了时间戳,这里不再展开
import base64
import requests
class XueXiTong(object):
def __init__(self, username, password):
self.url = "https://passport2.chaoxing.com/fanyalogin"
self.session = requests.session()
self.data = {
"uname": username,
"password": base64.b64encode(password.encode()),
"t": "true",
"forbidotherlogin": "0",
"fid": "-1",
"refer": "http%3A%2F%2Fi.chaoxing.com"
}
def run(self):
response = self.session.post(self.url, data=self.data)
print(response.content.decode())
# 带上上一次的cookie后再次发送请求,下面已经会出现个人信息,说明登陆成功
res = self.session.get('http://i.chaoxing.com')
print(res.content.decode())
if __name__ == '__main__':
username = '用户名' # 填写自己的用户名(登陆时使用的)
password = '密码' # 填写自己的密码
xuexitong = XueXiTong(username, password)
xuexitong.run()
数据提取
数据分类
结构化数据:
json数据(高频):处理方法有json模块,re模块,jsonpath模块
xml数据(低频):处理方法有re模块,lxml模块
非结构化数据:
html:处理方法有re模块,lxml模块,beautifulSoup模块等
xml:可扩展标记语言,用于数据的传输
html:超文本标记语言,用于数据的显示
jsonpath模块
-
作用:直接提取多层字典中的数据
-
jsonpath语法规则
$: 根节点
. 子节点(相邻层级)
.. 任意子节点(可跳跃层级)
import json
import jsonpath
import requests
url = "https://www.lagou.com/lbs/getAllCitySearchLabels.json"
# 请求头, 模拟浏览器发送请求
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}
response = requests.get(url, headers=headers)
# 全部数据
print(json.loads(response.content))
# A开头的城市
print(jsonpath.jsonpath(json.loads(response.content), '$..A..name'))
# 全部城市
print(jsonpath.jsonpath(json.loads(response.content), '$..name'))
lxml模块
-
对html和xml形式的文本利用XPath规则语法,快速定位元素或节点信息
-
XPath-helper插件
-
XPath语法
/ 绝对路径
// 相对路径
. 当前层节点
.. 上一层节点
/text() 取出标签中的文本
/@属性名 取出属性值
/div[i] 选中当前层的第i个div
/div[last()] 选中当前层最后一个div
/div[last()-1] 选中当前层倒数第2个div
div[position()>5] 选中第5个位置后的div
/div[@id='1'] 选中id为1的div
/div[contains(@id, '1')] 选中id包含1的div
/div[span='2'] 选中div中span值为'2'的div
/div[contains(text(), '南京')] 选中文本中包含'南京'的div
* 通配符
| 或
- 小练习1(爬取信大软院新闻)
import requests
from lxml import etree
url = 'https://scs.nuist.edu.cn'
response = requests.get(url)
html = etree.HTML(response.content)
# 取出南信大计软院的主页中包含"国庆"的链接的标题
print(html.xpath("//a[contains(text(), '国庆')]/text()")[0])
# 取出南信大计软院的主页中包含"国庆"的链接的url地址, 需要简单的拼接一下
print(url + html.xpath("//a[contains(text(), '国庆')]/@href")[0])
# 运行结果:
# 国庆┃筑梦灯火万家,同庆盛世繁华,热烈庆祝中华人民共和国成立72周...
# https://scs.nuist.edu.cn//2021/0930/c5922a183248/page.htm
- 小练习2(爬取信大软院新闻)
import requests
from lxml import etree
class Ad(object):
def __init__(self, name):
self.url = 'https://scs.nuist.edu.cn/{}/list.htm'.format(name)
def getData(self, url):
return requests.get(url).content
# 解析数据
def parseData(self, data):
html = etree.HTML(data)
el_list = html.xpath('//li/div/a')
data_list = []
for el in el_list:
temp = {}
temp['标题'] = el.xpath('./@title')[0]
temp['链接'] = 'https://scs.nuist.edu.cn' + el.xpath('./@href')[0]
data_list.append(temp)
# 获取下一页url, 有可能没有下一页
try:
next_url = 'https://scs.nuist.edu.cn' + html.xpath('//a[3]/@href')[0]
except:
next_url = None
return data_list, next_url
def saveData(self, data_list):
for data in data_list:
print(data)
def run(self):
next_url = self.url
while True:
# 有可能下一页的url不能访问
try:
data = self.getData(next_url)
except:
break
data_list, next_url = self.parseData(data)
self.saveData(data_list)
if next_url == None:
break
if __name__ == '__main__':
# 'tzgg':通知公告, 'ktdt':科研动态, 'jwxx':教务信息, 'xzfc':学子风采
ad = Ad('xzfc')
ad.run()
- etree.HTML(str)能够自动补全缺失的标签,etree.tostring(html)可以再转换为html格式
Selenium自动化测试框架
selenium基础
-
selenium能够大幅度降低爬虫的编写难度,但同时会大幅降低爬虫的爬取速度,非必要不使用
-
selenium是一个Web的自动化测试工具,最初是为了网站自动化测试而开发
-
selenium可以直接调用浏览器,支持所有主流浏览器(包括PhantomJS等无界面浏览器)
-
selenium可以接受指令,让浏览器自动加载界面,获取需要的数据,甚至页面截屏等
-
example:建议使用谷歌浏览器,其余浏览器自行选择,(360,qq等浏览器请绕行)
from selenium import webdriver
# 有界面的谷歌浏览器,配置环境变量(重启计算机)后可不添加参数
driver = webdriver.Chrome()
# 无界面的phantomjs浏览器,该浏览器已被selenium弃用,建议使用谷歌火狐等浏览器
# driver = webdriver.PhantomJS()
# 其余浏览器,以Edge浏览器为例,如果驱动路径已添加至环境变量,可不加参数
# driver = webdriver.Edge(executable_path="驱动路径")
# 发送请求
driver.get('https://scs.nuist.edu.cn/')
# 保存页面截图
driver.save_screenshot('01.png')
# 打印页面的标题
print(driver.title)
# 退出关闭进程,也可以手动关闭
driver.quit()
- 说明:如果仅是业余爬虫爱好者,使用chrome浏览器即可,但若将项目部署在服务器上时,则必须使用无界面浏览器
selenium的工作原理
-
安装driver驱动是控制浏览器的必要条件,不同浏览器因为厂商不同需要安装不同的driver驱动
-
流程:python代码–>调用不同浏览器的webdriver–>控制浏览器实现相应功能
-
example:
from selenium import webdriver
import time
# 实例化一个浏览器对象
driver = webdriver.Chrome()
# 控制浏览器访问一个url
driver.get('https://www.baidu.com/')
# 休眠3秒
time.sleep(3)
# 在浏览器搜索框中搜索南信大
driver.find_element_by_id('kw').send_keys('南信大')
time.sleep(2)
# 点击百度搜索
driver.find_element_by_id('su').click()
time.sleep(6)
# 休眠6秒后退出浏览器
driver.quit()
selenium的使用
- driver对象的常用属性和方法
driver.page_source: 显示当前标签页渲染过后的源码(省略了抓包分析的过程,所以使用极其简单)
driver.current_url: 显示当前标签页的url
driver.title: 显示当前标签页的标题
driver.forward(): 前进
driver.back(): 后退
driver.close(): 关闭页面
driver.quit(): 退出浏览器
driver.save_screenshot(filename): 网页截图
上述常用方法使用极其简单,当前不再一一验证,后续会组合使用
截图使用场景之一:截取验证码,并识别
- 定位标签元素的方法
定位一个元素 定位多个元素 含义
find_element_by_id() find_elements_by_id() 通过元素id定位
find_element_by_name() find_elements_by_name() 通过元素name定位
find_element_by_xpath() find_elements_by_xpath() 通过xpath表达式定位
find_element_by_link_text() find_elements_by_link_text() 通过完整超链接定位
find_element_by_partial_link_text() find_elements_by_partial_link_text() 通过部分链接定位
find_element_by_tag_name() find_elements_by_tag_name() 通过标签定位
find_element_by_class_name() find_elements_by_class_name() 通过类名进行定位
find_elements_by_css_selector() find_elements_by_css_selector() 通过CSS选择器定位标签
上述常用方法使用极其简单,当前不再一一验证,后续会组合使用
- 标签对象提取文本内容和属性值
element.text 获取文本内容
element.get_attribute('href') 获取属性值
element.click() 点击
element.send_keys() 发送信息到输入框
element.clear() 清空输入框
- 综合实例之信大公告
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://bulletin.nuist.edu.cn/791/list.htm')
with open('信息公告.csv', 'w', encoding='utf-8') as f:
f.write('标题,发布日期,链接\n')
# 获取南信大前三页的信息公告标题,发表日期,链接
for page in range(3):
print('*'*10 + '正在获取第{}页内容...'.format(page+1))
# 定位标签
ret1 = driver.find_elements_by_xpath('//*[@id="container"]/div/div[2]/div[2]/div//li/span[1]/span[4]/a')
ret2 = driver.find_elements_by_xpath('//*[@id="container"]/div/div[2]/div[2]/div//li/span[3]/span/a')
# 提取内容
for i, j in zip(ret1, ret2):
# print('标题:', i.text, '发布日期:', j.text, '链接:', i.get_attribute('href'))
with open('信息公告.csv', 'a', encoding='utf-8') as f:
f.write(i.text + ',' + j.text + ',' + i.get_attribute('href') + '\n')
# 点击下一页
driver.find_element_by_class_name('next').click()
driver.quit()
selenium的其它使用
标签页的切换
-
当selenium控制浏览器打开多个标签页时,有时浏览器发生了跳转,但selenium却没有跳转(小坑,写爬虫时需要注意)
-
切换步骤:
-
获取所有标签页的窗口句柄
-
利用窗口句柄切换到句柄指向的标签页
-
-
example:
from selenium import webdriver
url = 'https://nj.58.com/'
driver = webdriver.Chrome()
driver.get(url)
# 打印当前页面的url和窗口句柄
print(driver.current_url)
print(driver.window_handles)
# 定位并点击租房按钮
driver.find_element_by_xpath('/html/body/div[3]/div[1]/div[1]/div/div[1]/div[1]/span[1]/a').click()
# 再次打印当前页面url发现并未跳转,但窗口句柄发生了变化
print(driver.current_url)
print(driver.window_handles)
# 切换窗口句柄,新打开的窗口一般再句柄列表的最后
driver.switch_to.window(driver.window_handles[-1])
# 再次检查当前页面的url
print(driver.current_url)
rets = driver.find_elements_by_xpath('//div[6]/div[2]/ul/li/div[2]/h2/a')
for ret in rets:
print(ret.text, ret.get_attribute('href'))
driver.quit()
frame小窗口的切换
-
frame窗口内的标签并不能在页面文件中直接定位,需要先切换至该frame窗口下,再进行定位
-
以登录QQ空间为例:
import time
from selenium import webdriver
url = 'https://qzone.qq.com/'
driver = webdriver.Chrome()
driver.get(url)
# 通过id定位frame
# driver.switch_to.frame('login_frame')
# 也可以通过XPath路径定位
path = driver.find_element_by_xpath('//*[@id="login_frame"]')
driver.switch_to.frame(path)
driver.find_element_by_id('switcher_plogin').click()
driver.find_element_by_id('u').send_keys('QQ号') # 输入自己的QQ号
driver.find_element_by_id('p').send_keys('QQ密码') # 输入自己的QQ密码
driver.find_element_by_id('login_button').click()
time.sleep(10)
driver.quit()
获取cookie值
获取到的cookie可以配合requests模块使用,提高性能
from selenium import webdriver
url = 'https://www.baidu.com'
driver = webdriver.Chrome()
driver.get(url)
# 获取所有的cookie
print(driver.get_cookies())
# 删除多余的信息,并转换为字典
cookies = {data['name']: data['value'] for data in driver.get_cookies()}
print(cookies)
driver.quit()
执行js代码
有时浏览器并不能直接执行点击操作,需要下拉滚动条,此时需要执行js代码
小练习:点击51job主页下面的 “关于我们” 的链接
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.51job.com/')
# (水平滚动的距离, 竖直滚动的距离)
js = 'window.scrollTo(0, document.body.scrollHeight)'
# 执行js代码
driver.execute_script(js)
time.sleep(2)
driver.find_element_by_xpath('//div[8]//div[2]/a[1]').click()
time.sleep(3)
driver.quit()
页面等待
-
强制等待
-
使用方法:time.sleep()
-
缺点:不够灵活
-
-
隐式等待
-
说明:针对元素定位,设置了一个等待时间,在该时间内定位成功执行下一步,否则报错超时加载
-
使用方法:driver.implicitly_wait(10)
-
只需设置一次
-
-
显式等待
-
每隔一段时间就停止检查条件,达成则停止等待,否则继续等待直至超时
-
不是很重要,而且相对比较复杂,了解即可
-
开启无界面模式
-
无界面浏览器与有界面浏览器存在一定差别,使用时可能会遭到拦截。
-
但服务器大多是无界面的,所以使用有界面浏览器的无界面模式显得十分有必要
-
example:
from selenium import webdriver
# 创建配置对象
opt = webdriver.ChromeOptions()
# 添加配置参数
opt.add_argument('--headless') # 开启无头模式
opt.add_argument('--disable-gpu') # 不使用gpu
# 实例化浏览器对象时添加配置对象
driver = webdriver.Chrome(options=opt)
driver.get('https://www.nuist.edu.cn/')
driver.save_screenshot('信大主页.png')
ip代理
-
opt.add_argument(’–proxy-server=代理ip’)
-
使用方法参考无头模式,不再演示
更换user-agent
-
opt.add_argument(’–user-agent=???’)
-
使用方法参考无头模式,不再演示
综合实例之爬取京东商品信息
-
理论上下列程序可以爬取任意商品信息
-
但商品名称过多,本人难以一一验证,所以不能保证其普适的正确性
-
如部分商品爬取出现错误,请根据实际情况手动改动即可
-
本人已验证手机,电脑,键盘,鼠标等商品,其余商品待测
import time
from selenium import webdriver
class JD(object):
def __init__(self, message):
self.url = 'https://www.jd.com/'
# 创建配置对象
opt = webdriver.ChromeOptions()
# 添加配置参数
opt.add_argument('--headless') # 开启无头模式
opt.add_argument('--disable-gpu') # 不使用gpu
self.driver = webdriver.Chrome(options=opt)
self.message = message
# 隐式等待,建议加上,否则可能由于加载原因导致定位不到元素
self.driver.implicitly_wait(10)
with open('京东{}商品信息.csv'.format(message), 'w', encoding='utf-8') as f:
f.write('{}信息介绍, 价格, 链接\n'.format(message))
def find(self):
self.driver.get(self.url)
self.driver.find_element_by_xpath('//*[@id="key"]').send_keys(self.message)
self.driver.find_element_by_xpath('//*[@id="search"]/div/div[2]/button').click()
def getData(self):
# 每隔0.2秒下拉滚动条,进行动态加载
for i in range(20):
self.driver.execute_script('window.scrollBy(0, 500)')
time.sleep(0.2)
# 简介
data1 = self.driver.find_elements_by_xpath('//*[@id="J_goodsList"]/ul/li//a/em')
# 价格
data2 = self.driver.find_elements_by_xpath('//*[@id="J_goodsList"]//li/div//div[@class="p-price"]')
# 商品链接
data3 = self.driver.find_elements_by_xpath('//*[@id="J_goodsList"]/ul/li//div[@class="p-img"]/a')
return data1, data2, data3,
def saveData(self, data1, data2, data3):
with open('京东{}商品信息.csv'.format(self.message), 'a', encoding='utf-8') as f:
for i, j, k in zip(data1, data2, data3):
# 每一页商品列表有60项,但少部分商品有多选项(这里忽略)会导致出现空值
if len(data1) > 60 and i.text.replace(' ', '').replace('\n', '') == '':
continue
f.write('{},{},{}\n'.format(i.text.replace('\n', ' '), j.text.replace('\n', ''), k.get_attribute('href')))
def run(self):
self.find()
i = 1
while True:
print('正在爬取第{}页商品信息...'.format(i))
data1, data2, data3 = self.getData()
self.saveData(data1, data2, data3)
try:
self.driver.find_element_by_xpath('//*[@id="J_bottomPage"]/span[1]/a[@class="pn-next"]').click()
i = i + 1
except:
break
self.driver.quit()
if __name__ == '__main__':
message = '手机' # 输入商品名称
jd = JD(message)
jd.run()
反爬虫
反爬原因
-
流量压力,会给对方服务器造成巨大压力,严重者会影响正常用户体验
-
竞争压力,恶意竞争
-
法律压力,法律未明令禁止,不受网络安全法约束
反爬对象
-
学生党的低级小爬虫
-
初创公司的低级小爬虫
-
托管在服务器上的无主爬虫
-
竞争对手的所有爬虫
-
失控的搜索引擎
目标:禁止一切爬虫
反爬方向
基于身份识别
-
通过headers字段来反爬
-
User-Agent
反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置 解决方法:添加上即可或者使用User-Agent池
-
referer
反爬原理:爬虫默认情况下没有referer字段,服务器可以判断请求发起的源头 解决方法:添加上即可
-
cookie
反爬原理:通过检查cookies来查看发起请求的用户是否具有相应权限 解决方法:进行模拟登录,成功获取cookies之后再进行数据的爬取
-
-
通过请求参数来反爬
服务器可以通过检查请求参数是否正确来判断是否为爬虫
-
从静态文件中获取请求数据
-
通过发送请求获取请求数据
-
通过js生成请求参数
-
通过验证码来反爬
-
基于爬虫行为
-
基于请求频率或总请求数量(爬虫远高于普通用户)
- 通过请求ip/账号单位时间内总请求数量
反爬原理:正常浏览器请求网站,速度不会太快 解决方法:购买ip或者账号(确保自己ip和账号的安全)
- 通过同一ip/账号请求之间的间隔
反爬原理:正常人使用浏览器请求网站,时间间隔是随机的,而程序间隔一般是固定的且较短 解决方法:添加随机间隔,但会降低性能,必要时可以使用代理池或者多个账号
- 通过请求ip/账号每天请求次数设置阈值
反爬原理:正常的浏览行为,一天的浏览次数是有限的,超过阈值服务器会拒绝响应 解决方法:多ip/账号,请求时间随机休眠
-
基于爬虫行为(步骤)
- 通过js实现跳转
反爬原理:js实现页面跳转,无法在源码中获取下一页url 解决方法:多次抓包,找到url之间的规律
- 通过设置陷阱
反爬原理:设置标签的不可见属性,正常人不会浏览,而爬虫会中计 解决方法:找出陷阱,做出相应规避
- 通过假数据
反爬原理:添加假数据污染数据库,正常人无法看到假数据 解决方法:长期运行,分析数据
- 阻塞任务队列
反爬原理:生成大量的垃圾url,浪费爬虫时间 解决方法:过滤垃圾url
- 阻塞网络IO
反爬原理:发送请求获取响应的过程就是下载的过程,虚假url会跳转至大文件(如下载蓝光电影)从而占用网络IO 解决方法:分析爬虫运行状态,多线程时可以分析线程运行时间,时间过长直接终止线程
-
运维平台综合审计
爬虫工程师与运维工程师的之间的对抗
基于加密数据
-
对响应中的数据进行特殊化处理
-
通过自定义字体(可以切换到手机端抓包进行翻译)
-
css偏移量
反爬原理:源码数据不是真实数据,通过css位移才能获得真实数据 解决方法:计算css位移
-
js动态生成数据(解析js,模拟产生数据)
-
数据图片化(解析图片)
-
编码格式(多编码解析)
-
验证码处理
图片验证码
-
验证码:区分用户是计算机还是人的程序
-
作用:防止恶意破解密码,刷票等
-
使用场景:注册,登录,频繁发生请求
-
处理方案:手动输入,图像识别引擎,打码平台
图片识别引擎
OCR:使用扫描仪或者数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件
-
tesseract:一款由HP实验室开发由google维护的OCR引擎,开源免费,支持多语言多平台
-
example:
from PIL import Image
import pytesseract
im = Image.open('../Data/1.png')
result = pytesseract.image_to_string(im)
print(result)
打码平台
以下打码平台已经被禁止,虽然网上还有其他平台,但仍会遭受打击且收费,这里不在赘述使用方法
-
云打码:能解决通用的验证码识别
-
极验验证码智能识别辅助:能解决复杂的验证码识别
JS的解析
确定js的位置
-
对手机端进行抓包分析更简单
-
通过initiator定位到js文件
-
通过搜索关键字定位到js文件
-
通过元素绑定的事件定位到js文件
分析js代码(逆向)
没啥好讲的pass(极其头疼的一部分,劝退)
模拟重现
- 通过第三方js加载模块直接加载js运行
import js2py
# 创建js环境对象
js = js2py.EvalJs()
js.execute('''
function fun (e) {
var r = "" + (new Date).getTime()
return {
ts: r,
}
};''')
js.execute('console.log("hello world")')
js.execute('''console.log(fun())''')
- 用python重现js过程(要求扎实的逆向能力和良好的python素养)
这一部分感兴趣的自己学
数据库
框架
分布式
appium(移动设备爬取)
感言
转方向了,累了,已经可以做些low爬虫了