三、抓包工具Fiddler
Fiddler是一款强大Web调试工具,它能记录所有客户端和服务器的HTTP请求。 Fiddler启动的时候,默认IE的代理设为了127.0.0.1:8888,而其他浏览器是需要手动设置。
工作原理
Fiddler 是以代理web服务器的形式工作的,它使用代理地址:127.0.0.1,端口:8888
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxtdABad-1572593770696)(assets/fidder_pro.jpg)]
Fiddler抓取HTTPS设置
-
启动Fiddler,打开菜单栏中的 Tools > Telerik Fiddler Options,打开“Fiddler Options”对话框。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N66kDaWl-1572593770696)(assets/01-fidder.png)]
- 对Fiddler进行设置:
- 打开工具栏->Tools->Fiddler Options->HTTPS,
- 选中Capture HTTPS CONNECTs (捕捉HTTPS连接),
- 选中Decrypt HTTPS traffic(解密HTTPS通信)
- 另外我们要用Fiddler获取本机所有进程的HTTPS请求,所以中间的下拉菜单中选中…from all processes (从所有进程)
-
选中下方Ignore server certificate errors(忽略服务器证书错误)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okMv8ppB-1572593770697)(assets/01-fidder_01-1559532796189.png)]
-
为 Fiddler 配置Windows信任这个根证书解决安全警告:Trust Root Certificate(受信任的根证书)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcQpOsmV-1572593770697)(assets/01-fidder_03.png)]
- Fiddler 主菜单 Tools -> Fiddler Options…-> Connections
- 选中Allow remote computers to connect(允许远程连接)
-
Act as system proxy on startup(作为系统启动代理)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwZ9290k-1572593770697)(assets/01-fidder_02.png)]
- 重启Fiddler,使配置生效(这一步很重要,必须做)。
Fiddler 如何捕获Chrome的会话
-
安装SwitchyOmega 代理管理 Chrome 浏览器插件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OdSZsjYu-1572593770697)(assets/switchyomega.png)]
-
如图所示,设置代理服务器为127.0.0.1:8888
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODGyk3ik-1572593770698)(assets/switchyomega_setting.png)]
-
通过浏览器插件切换为设置好的代理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9PakEi7-1572593770698)(assets/SwitchyOmega_switch.png)]
Fiddler界面
设置好后,本机HTTP通信都会经过127.0.0.1:8888代理,也就会被Fiddler拦截到。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNqFjloS-1572593770698)(assets/fiddler_show.png)]
请求 (Request) 部分详解
- Headers —— 显示客户端发送到服务器的 HTTP 请求的 header,显示为一个分级视图,包含了 Web 客户端信息、Cookie、传输状态等。
- Textview —— 显示 POST 请求的 body 部分为文本。
- WebForms —— 显示请求的 GET 参数 和 POST body 内容。
- HexView —— 用十六进制数据显示请求。
- Auth —— 显示响应 header 中的 Proxy-Authorization(代理身份验证) 和 Authorization(授权) 信息.
- Raw —— 将整个请求显示为纯文本。
- JSON - 显示JSON格式文件。
- XML —— 如果请求的 body 是 XML 格式,就是用分级的 XML 树来显示它。
响应 (Response) 部分详解
- Transformer —— 显示响应的编码信息。
- Headers —— 用分级视图显示响应的 header。
- TextView —— 使用文本显示相应的 body。
- ImageVies —— 如果请求是图片资源,显示响应的图片。
- HexView —— 用十六进制数据显示响应。
- WebView —— 响应在 Web 浏览器中的预览效果。
- Auth —— 显示响应 header 中的 Proxy-Authorization(代理身份验证) 和 Authorization(授权) 信息。
- Caching —— 显示此请求的缓存信息。
- Privacy —— 显示此请求的私密 (P3P) 信息。
- Raw —— 将整个响应显示为纯文本。
- JSON - 显示JSON格式文件。
- XML —— 如果响应的 body 是 XML 格式,就是用分级的 XML 树来显示它 。
—————————————————————————————————————————————————
四、urllib和urllib2库的基本使用
所谓网页抓取,就是把URL地址中指定的网络资源从网络流中抓取出来。在Python中有很多库可以用来抓取网页,我们先学习
urllib2
。
urllib2 是 Python2.7 自带的模块(不需要下载,导入即可使用)
urllib2 官方文档:https://docs.python.org/2/library/urllib2.html
urllib2 源码:https://hg.python.org/cpython/file/2.7/Lib/urllib2.py
在 python3 中,urllib2 被改为urllib.request
urlopen
我们先来段代码:
# 创建一个py文件:urllib2_urlopen.py
# 导入urllib2 库
import urllib2
# 向指定的url发送请求,并返回服务器响应的类文件对象
response = urllib2.urlopen("http://www.baidu.com")
# 类文件对象支持 文件对象的操作方法,如read()方法读取文件全部内容,返回字符串
html = response.read()
# 打印字符串
print(html)
执行写的python代码,将打印结果
[email protected]:~/Desktop/demo$ python urllib2_urlopen.py
实际上,如果我们在浏览器上打开百度主页, 右键选择“查看源代码”,你会发现,跟我们刚才打印出来的是一模一样。也就是说,上面的4行代码就已经帮我们把百度的首页的全部代码爬了下来。
一个基本的url请求对应的python代码真的非常简单。
Request
在我们第一个例子里,urlopen()的参数就是一个url地址;
但是如果需要执行更复杂的操作,比如增加HTTP报头,必须创建一个 Request 实例来作为urlopen()的参数;而需要访问的url地址则作为 Request 实例的参数。
我们编辑urllib2_request.py
# urllib2_request.py
import urllib2
# url 作为Request()方法的参数,构造并返回一个Request对象
request = urllib2.Request("http://www.baidu.com")
# Request对象作为urlopen()方法的参数,发送给服务器并接收响应
response = urllib2.urlopen(request)
html = response.read()
print(html)
运行结果是完全一样的:
新建Request实例,除了必须要有 url 参数之外,还可以设置另外两个参数:
- data(默认空):提交的Form表单数据,同时 HTTP 请求方法将从默认的 "GET"方式 改为 "POST"方式。
- headers(默认空):参数为字典类型,包含了需要发送的HTTP报头的键值对。
User-Agent
但是这样直接用urllib2给一个网站发送请求的话,确实略有些唐突了,就好比,人家每家都有门,你以一个路人的身份直接闯进去显然不是很礼貌。而且有一些站点不喜欢被程序(非人为访问)访问,有可能会拒绝你的访问请求。
但是如果我们用一个合法的身份去请求别人网站,显然人家就是欢迎的,所以我们就应该给我们的这个代码加上一个身份,就是所谓的
User-Agent
头。
- 浏览器 就是互联网世界上公认被允许的身份,如果我们希望我们的爬虫程序更像一个真实用户,那我们第一步就是需要伪装成一个被浏览器。用不同的浏览器在发送请求的时候,会有不同的 User-Agent 报头。
- urllib2默认的User-Agent头为:
(x和y 是Python 主.次 版本号,例如 Python-urllib/2.7)
Python-urllib/x.y
# coding:utf-8
#urllib2_useragent.py
import urllib2
url = "http://www.baidu.com"
# IE 9.0 的 User-Agent,包含在 user_agent里
user_agent = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"}
# url 连同 headers,一起构造Request请求,这个请求将附带 IE9.0 浏览器的User-Agent
request = urllib2.Request(url, headers = user_agent)
# 向服务器发送这个请求
response = urllib2.urlopen(request)
html = response.read()
print(html)
添加更多的Header信息
在 HTTP Request 中加入特定的 Header,来构造一个完整的HTTP请求消息。
可以通过调用添加/修改一个特定的header 也可以通过调用
Request.add_header()
来查看已有的header。
Request.get_header()
- 添加一个特定的header
# coding:utf-8
# urllib2_headers.py
import urllib2
url = "http://www.baidu.com"
#IE 9.0 的 User-Agent
user_agent = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"}
request = urllib2.Request(url, headers = user_agent)
#也可以通过调用Request.add_header() 添加/修改一个特定的header
request.add_header("Connection", "keep-alive")
# 也可以通过调用Request.get_header()来查看header信息
# request.get_header(header_name="Connection")
response = urllib2.urlopen(request)
print(response.code #可以查看响应状态码)
html = response.read()
print(html)
- 随机添加/修改User-Agent
# urllib2_add_headers.py
import urllib2
import random
url = "http://www.baidu.com"
ua_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6"
]
user_agent = random.choice(ua_list)
request = urllib2.Request(url)
#也可以通过调用Request.add_header() 添加/修改一个特定的header
request.add_header("User-Agent", user_agent)
# get_header()的字符串参数,第一个字母大写,后面的全部小写
request.get_header("User-agent")
response = urllib2.urlopen(request)
html = response.read()
print(html)
—————————————————————————————————————————————————
(一)URL编码转换
一般HTTP请求提交数据,需要将数据编码成 URL编码格式,然后做为查询字符串或者表单参数,构建Request对象中再发送。
urllib 和 urllib2 都是接受URL请求的相关模块,但是提供了不同的功能。两个最显著的不同如下:
- urllib 模块仅可以接受URL,不能创建 设置了headers 的Request 类实例;
- 但是 urllib 提供
方法用来产生GET查询字符串,而 urllib2 则没有。(这是 urllib 和 urllib2 经常一起使用的主要原因)
urlencode
- 编码工作使用urllib的
函数,帮我们将
urlencode()
这样的键值对,转换成
key:value
这样的字符串,解码工作可以使用urllib的
"key=value"
函数。( 注意,不是urllib2.urlencode())
unquote()
# IPython2 中的测试结果
In [6]: import urllib
# 通过urllib.urlencode()方法,将字典键值对按URL编码转换,从而能被web服务器接受。
In [7]: a = {"wd":"杭州很美"}
In [8]: urllib.urlencode(a)
Out[8]: 'wd=%E6%9D%AD%E5%B7%9E%E5%BE%88%E7%BE%8E'
# 通过urllib.unquote()方法,把 URL编码字符串,转换回原先字符串。
In [9]: print(urllib.unquote('wd=%E6%9D%AD%E5%B7%9E%E5%BE%88%E7%BE%8E'))
wd=杭州很美
——————————————————————————————————————————————————
六、Get请求
GET请求一般用于我们向服务器获取数据,比如说,我们用百度搜索
杭州好美
:
https://www.baidu.com/s?wd=“杭州好美”
浏览器的url会跳转成如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVcYFFce-1572593770699)(assets/01-shousuo.png)]
https://www.baidu.com/s?wd=%22%E6%9D%AD%E5%B7%9E%E5%A5%BD%E7%BE%8E%E2%80%9C
在其中我们可以看到在请求部分里,
http://www.baidu.com/s?
之后出现一个长长的字符串,其中就包含我们要查询的关键词–杭州很美,于是我们可以尝试用默认的Get方式来发送请求。
# urllib2_get.py
import urllib #负责url编码处理
url = "http://www.baidu.com/s?"
keyword = {"wd":"杭州好美"}
word = urllib.urlencode(keyword) #转换成url编码格式(查询字符串)
newurl = url + word # 拼接url
headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"}
request = urllib2.Request(newurl, headers=headers)
response = urllib2.urlopen(request)
print response.read()
批量爬取贴吧页面数据
首先我们创建一个python文件, tiebaSpider.py,我们要完成的是,输入一个百度贴吧的地址,以及起始页和结束页,就能将所有的网页源码下载并保存到本地。
比如百度贴吧LOL吧:
第一页:http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=0
第二页: http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=50
第三页: http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=100
1.发现规律了吧,贴吧中每个页面不同之处,就是url最后的pn的值,其余的都是一样的,我们可以抓住这个规律来写代码。
def test():
"""测试文件,通过获取输入起始页,结束页,获取pn值"""
begin_page = int(input("请输入起始页:"))
end_page = int(input("请输入结束页:"))
# 循环获取每个页码值
for page in range(begin_page, end_page + 1):
# 计算得出当前页码的 pn 值
pn = (page - 1) * 50
# 检测pn值是否正确
print(pn)
if __name__ == '__main__':
test()
简单写一个小爬虫程序,来爬取百度LOL吧的所有网页。
- 提示用户输入要爬取的贴吧名,起始页,结束页
- 写一个main函数
- main函数里组合的拼接后的url地址,以及起始页码和终止页码,表示要爬取页码的范围。
- 并用urllib.urlencode()进行转码
- 然后组合url,假设用户输入的贴吧名是lol,起始页是1, 结束页是大于1的值(例如3)
- 组合后的起始url就是:
http://tieba.baidu.com/f?kw=lol&pn=0
from urllib import parse
tieba_name = input("请输入贴吧名:")
begin_page = int(input("请输入起始页:"))
end_page = int(input("请输入结束页:"))
base_url = "http://tieba.baidu.com/f?"
def main():
# 循环获取每个页码值
for page in range(begin_page, end_page + 1):
# 计算得出当前页码的 pn 值
pn = (page - 1) * 50
query_dict = {"kw": tieba_name, "pn": pn}
# 从urllib包获取parse模块, 里面的urlencode方法
# 将字典转换为查询字符串
query_str = parse.urlencode(query_dict)
# 拼接base_url 和查询字符串,构建完整的url地址
full_url = base_url + query_str
print(full_url)
if __name__ == '__main__':
main()
- 接下来,我们写一个百度贴吧爬虫接口,url传入send_request函数,函数里用urlopen方法实现爬取网页的功能,并返回一个响应对象。通过response.read()获取响应内容
def send_request(url):
"""
接收url地址,发送请求,返回响应
"""
request = urllib.request.Request(url, headers = headers)
print("[INFO]: 正在发送请求 {}..".format(url))
response = urllib.request.urlopen(request)
print(response.read())
return response
- 最后如果我们希望将爬取到了每页的信息存储在本地磁盘上,我们可以简单写一个存储文件的接口。
def save_page(response, file_name):
"""
保存响应内容到指定文件中
"""
print("[INFO]: 正在保存数据 {}..".format(file_name))
with open(file_name, "w") as f:
f.write(response.read().decode())
其实很多网站都是这样的,同类网站下的html页面编号,分别对应网址后的网页序号,只要发现规律就可以批量爬取页面了。
#最终的代码
#coding:utf-8
import urllib
from urllib import request, parse
base_url = "http://tieba.baidu.com/f?"
headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"}
tieba_name = input("请输入贴吧名:")
begin_page = int(input("请输入起始页:"))
end_page = int(input("请输入结束页:"))
def send_request(url):
"""
接收url地址,发送请求,返回响应
"""
request = urllib.request.Request(url, headers = headers)
print("[INFO]: 正在发送请求 {}..".format(url))
response = urllib.request.urlopen(request)
return response
def save_page(response, file_name):
"""
保存响应内容到指定文件中
"""
print("[INFO]: 正在保存数据 {}..".format(file_name))
with open(file_name, "w") as f:
f.write(response.read().decode())
def main():
"""
爬虫调度中心
"""
# 循环获取每个页码值
for page in range(begin_page, end_page + 1):
# 计算得出当前页码的 pn 值
pn = (page - 1) * 50
# 构建查询字符串
query_dict = {"kw" : tieba_name, "pn" : pn}
query_str = urllib.parse.urlencode(query_dict)
# 拼接base_url 和查询字符串,构建完整的url地址
full_url = base_url + query_str
file_name = tieba_name + str(page) + ".html"
try:
# 将url地址传给send_request() 发送请求,返回响应
response = send_request(full_url)
# 保存响应数据到文件中
save_page(response, file_name)
except Exception as e:
print("[ERROR] : {} 抓取失败.".format(full_url))
print(e)
if __name__ == '__main__':
main()
七、POST请求:
获取AJAX加载的内容
有些网页内容使用AJAX请求加载,这种数据无法直接对网页url进行获取。但是只要记住,AJAX请求一般返回给网页的是JSON文件,只要对AJAX请求地址进行POST或GET,就能返回JSON数据了。
如果非要从HTML页面里获取展现出来的数据,也不是不可以。但是要记住,作为一名爬虫工程师,你更需要关注的是数据的来源。
发送POST请求时,需要了解的headers一些属性:
: 是指发送的表单数据长度为100,也就是url编码字符串的字符个数是100个。
Content-Length: 100
: 表示浏览器提交 Web 表单时使用,表单数据会按照 name1=value1&name2=value2 键值对形式进行编码。
Content-Type: application/x-www-form-urlencoded
:表示AJAX异步请求
X-Requested-With: XMLHttpRequest
腾讯翻译君案例:
1.解析页面
输入要翻译的文本,显示出翻译内容,html发生了变化,其url没有发生改变,这是一个动态页面。
通过浏览器自带的抓包工具,抓取动态页面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c18sd9Q7-1572593770699)(assets/腾讯翻译01.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtFjhw4x-1572593770699)(assets/腾讯翻译02.png)]
2.分析数据
输入不同的文本内容,分析post携带的表单数据有何异同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rksbG7pa-1572593770700)(assets/腾讯翻译03.png)]
根据文本内容不同(例如,翻译框输入“你好” 和“中国”后对比表单信息),发现表单的所有字段都相同,唯有要翻译的文本信息和时间戳不一样,其他都相同,那我们可以针对这两个字段,替换成符合要求的内容。
"sourceText": input("请输入要翻译的内容")
# 时间戳
# 根据经验,15或16开头的10-15位数字,一般优先考虑是时间戳
"sessionUuid": "translate_uuid" + str(int(time.time() * 1000))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LARKNNKG-1572593770700)(assets/时间戳.png)]
尝试用POST方式发送请求
import json
import requests
import time
# post请求的url地址, 通过浏览器抓包获取
base_url = "https://fanyi.qq.com/api/translate"
headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Connection": "keep-alive",
"Content-Length": "294",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Cookie": "fy_guid=94af8e98-08f4-49a2-9562-1c93b5299969; qtv=c03f8898e63faaf1; qtk=c0VhySUiXAQjye4yzCLCCnS2VJiX3nO+PguX/CLuhKsDkPu2+aN5vr0fr0/6hfpi+jVIS4Z0Ys7bm4xK1jsYymyeF3qbhP1xI3kbKmqf1UBe/TnrdmhbwkYPdmjP61aqIZfIN89ZyLDagGo2fjNESg==; openCount=1; gr_user_id=6edf2548-7b4e-4c34-9c05-6831c2ebb552; 8507d3409e6fad23_gr_session_id=8577a037-6ae8-4d6e-b898-3eb27a84dc16; grwng_uid=d7ac9b1f-70f3-48b1-8e56-a1d004132d66; 8c66aca9f0d1ff2e_gr_session_id=c74395eb-6342-4057-8224-077764c5a5eb; 8507d3409e6fad23_gr_session_id_8577a037-6ae8-4d6e-b898-3eb27a84dc16=false",
"Host": "fanyi.qq.com",
"Origin": "https://fanyi.qq.com",
"Referer": "https://fanyi.qq.com/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"X-Requested-With": "XMLHttpRequest"
}
# 需要传递的表单数据
form_data = {
"source": "auto",
"target": "auto",
# 翻译的内容
"sourceText": input("请输入要翻译的内容"),
"qtv": "c03f8898e63faaf1",
"qtk": "c0VhySUiXAQjye4yzCLCCnS2VJiX3nO+PguX/CLuhKsDkPu2+aN5vr0fr0/6hfpi+jVIS4Z0Ys7bm4xK1jsYymyeF3qbhP1xI3kbKmqf1UBe/,TnrdmhbwkYPdmjP61aqIZfIN89ZyLDagGo2fjNESg==",
# 时间戳
"sessionUuid": "translate_uuid" + str(int(time.time() * 1000))
}
def send_request():
# requests是urllib的封装
response2 = requests.post(base_url, data=form_data, headers=headers)
return response2
def parse_response(response):
# 获取响应字符串
str_content = response.content.decode("utf-8")
# 将响应字符串转为Python数据类型
dict_json = json.loads(str_content)
print("翻译结果", dict_json["translate"]["records"][0]["targetText"])
def main():
response2 = send_request()
parse_response(response2)
if __name__ == '__main__':
main()
问题:GET和POST的区别?
GET方式是直接以链接形式访问,链接中包含了所有的参数,服务器端用Request.QueryString获取变量的值。如果包含了密码的话是一种不安全的选择,不过你可以直观地看到自己提交了什么内容。
requests是urllib的封装
response2 = requests.post(base_url, data=form_data, headers=headers)
return response2
def parse_response(response):
# 获取响应字符串
str_content = response.content.decode(“utf-8”)
# 将响应字符串转为Python数据类型
dict_json = json.loads(str_content)
print(“翻译结果”, dict_json[“translate”][“records”][0][“targetText”])
def main():
response2 = send_request()
parse_response(response2)
if name == ‘main’:
main()
------
### 问题:GET和POST的区别?
> - GET方式是直接以链接形式访问,链接中包含了所有的参数,服务器端用Request.QueryString获取变量的值。如果包含了密码的话是一种不安全的选择,不过你可以直观地看到自己提交了什么内容。
> - POST则不会在网址上显示所有的参数,服务器端用Request.Form获取提交的数据,在Form提交的时候。但是HTML代码里如果不指定 method 属性,则默认为GET请求,Form中提交的数据将会附加在url之后,以`?`分开与url分开。