爬虫第二课

mac2025-08-11  17

三、抓包工具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代码,将打印结果

python@ubuntu:~/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头为:Python-urllib/x.y (x和y 是Python 主.次 版本号,例如 Python-urllib/2.7) # 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请求消息。

可以通过调用Request.add_header() 添加/修改一个特定的header 也可以通过调用Request.get_header()来查看已有的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 提供 urlencode 方法用来产生GET查询字符串,而 urllib2 则没有。(这是 urllib 和 urllib2 经常一起使用的主要原因)编码工作使用urllib的urlencode()函数,帮我们将key:value这样的键值对,转换成"key=value"这样的字符串,解码工作可以使用urllib的unquote()函数。( 注意,不是urllib2.urlencode()) # 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一些属性:

Content-Length: 100: 是指发送的表单数据长度为100,也就是url编码字符串的字符个数是100个。

Content-Type: application/x-www-form-urlencoded : 表示浏览器提交 Web 表单时使用,表单数据会按照 name1=value1&name2=value2 键值对形式进行编码。

X-Requested-With: XMLHttpRequest :表示AJAX异步请求

腾讯翻译君案例:

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分开。
最新回复(0)