赞
踩
目录
在上节我们讲到了人人网的案例,发送携带表单数据的post请求可以成功访问到登录后的页面,如果我们此时想通过此页面去访问别人的主页我们应该怎么做?
- import requests
-
- if __name__ == '__main__':
- # 1.确认目标的url > 登录的url
- url_ = 'http://www.renren.com/PLogin.do'
-
- # 用户代理
- headers_ = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
- }
-
- # post请求携带数据,form表单..
- data_ = {
- 'email': 'xxxxxxxx', # 账号
- 'password': 'xxxxxxxx' # 密码
- }
-
- # 2.发送请求
- response_ = requests.post(url_, headers=headers_, data=data_)
- str_data = response_.text
-
- # 4.保存
- with open('renren02.html', 'w', encoding='utf-8') as f:
- f.write(str_data)
-
- # 找到详情页url_ > 需要登录之后才能够访问的
- url_ = 'http://www.renren.com/880151247/profile'
-
- response_ = requests.get(url_, headers=headers_)
- str_data = response_.text
-
- # 保存在本地
- with open('renren_dapeng.html', 'w', encoding='utf-8') as f:
- f.write(str_data)
通过上述代码我们可以成功访问到人人网登录后的个人主页,但是当访问别人主页的时候,又跳转到了登录页面。
出现这种情况的原因很简单,我们使用的python代码进行访问网站并不是浏览器,所以当我们去访问别人主页时并没有携带登陆后带有登录信息的cookie,这才导致我们访问失败。
搞清楚了原因后解决就变得轻松多了,我们只要在第二次发送请求时带上登陆后的cookie就可以成功了。但是这样的作法也存在着一些问题:
当我们要去访问100个人的主页时,我们要做100次的模拟登陆,然后再去携带100个不同的cookie,就很麻烦,此时我们引出一种更简单的做法。
利用requests的session方法,创建一个session类,利用session发送的请求就会自动携带登录过后保存有登录信息的cookie
- import requests
-
- if __name__ == '__main__':
- # 1.确认目标的url > 登录的url
- url_ = 'http://www.renren.com/PLogin.do'
-
- # 用户代理
- headers_ = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
- }
-
- # post请求携带数据,form表单..
- data_ = {
- 'email': 'xxxxxxx', # 账号
- 'password': 'xxxxxxx' # 密码
- }
-
- # 2.如果想要发送post请求,模拟了登录之后,自动携带登陆过后的cookie,就要使用session方法
- session_ = requests.session() # 返回一个session对象
- session_.post(url_, headers=headers_, data=data_) # 之后session对象就保持了登录信息
-
- # 4.保存
- # with open('renren02.html','w',encoding='utf-8') as f:
- # f.write(str_data)
-
- # 找到详情页url_ > 需要登录之后才能够访问的
- url_ = 'http://www.renren.com/880151247/profile' # dapeng
- # 此时的session是保持了登录状态的,自动携带了带有登录信息的cookie
- response_ = session_.get(url_, headers=headers_)
- str_data = response_.text
-
- # 保存在本地
- with open('renren_dapeng02.html', 'w', encoding='utf-8') as f:
- f.write(str_data)
构造方法与请求头的构造方法一致,也是一个键值对
- proxy_ = {
- # 固定写法
- 'http': 'http://1.1.1.1:8888',
- 'https': 'https://1.1.1.1:8888'
- }
键名为http或者https,值为 http://IP地址:端口号 或 https://IP地址:端口号
- proxy_ = {
- # 固定写法
- 'http': '1.1.1.1:8888',
- 'https': '1.1.1.1:8888'
- }
第二种较为简单并且常用,键名与第一种方法一样,值为 IP地址:端口号
- import requests
-
- if __name__ == '__main__':
- url_ = 'https://www.baidu.com/'
-
- headers_ = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
- }
-
- # 代理IP的构建,以键值对的方式
- # proxy_ = { # 固定的语法 ip地址:端口号
- # 'http':'1.1.1.1:8888'
- # }
- proxy_ = { # 固定的语法 ip地址:端口号
- 'http': 'http://1.1.1.1:8888' # 是一个无效的代理IP
- }
-
- response_ = requests.get(url_, headers=headers_, proxies=proxy_)
- bytes_data = response_.content
-
- # 保存一下
- with open('baidu01.html', 'wb') as f:
- f.write(bytes_data)
在访问网站时的用法如上
上述代码中的代理IP是我们瞎编的,为什么还有访问成功呢?因为requests最近更新后,当代理IP使用不成功时,会自动使用本机IP
测试代理IP的有效性:
测试网站:http://2021.ip138.com/
我们既然有了测试IP地址的网站,那么使用代理IP访问该网站就可以测试代理IP是否有效
- import requests
- from lxml import etree # 提取html格式数据的
-
- if __name__ == '__main__':
- # 特殊的测试IP的url
- url_ = 'http://2021.ip138.com/'
-
- headers_ = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
- }
-
- # 代理IP的构建,以键值对的方式
- # proxy_ = { # 固定的语法 ip地址:端口号
- # 'http':'1.1.1.1:8888'
- # }
- proxy_ = { # 固定的语法 ip地址:端口号
- 'http': 'http://1.1.1.1:8888' # 是一个无效的代理IP
- }
-
- response_ = requests.get(url_, headers=headers_, proxies=proxy_)
- # response_ = requests.get(url_,headers=headers_)
- # print(response_)
- str_data = response_.text # 得到字符串类型的响应文本数据
-
- # 提取数据(后面会讲)
- html_obj = etree.HTML(str_data)
- res_ = html_obj.xpath('//title/text()')[0]
- print(res_)
如果程序报错,就证明代理IP无效
当我们使用代理IP池时,我们可以通过循环测试他们的有效性,将有效的代理IP加入到一个空列表中,当循环结束时,这个列表中的代理IP就全是有效的了。
当我们访问网站的时候,会出现响应时间过长的情况。
例如:我们在测试代理IP有效性的时候,即使有些代理IP有效,但存在着访问网站获取响应速度过慢的情况,所以我们需要把这样的IP也剔除掉
response_ = requests.get(url_, headers=headers_, proxies=proxy_, timeout=3)
我们在发送请求时加上timeout参数就可以了,例子中 timeout=3 ,就是在3秒内没有获得响应的话就立即报错
见名知意,retrying重新尝试,即访问失败时重新发送请求
当我们要发送很多请求时,可能由于网络波动或者其他原因导致极少数的url请求不成功,那么我们是直接放弃这些url的请求吗?答案当然是否定的,我们可以使用retrying模块来实现多给他几次发送请求的机会
retrying模块是个第三方模块,需要自己下载。
我们使用的是retrying模块中的retry,通过查看retry,可以发现他是一个三层嵌套的闭包,用来做带有参数的装饰器,因为装饰器只能对函数和方法进行装饰,所以此处我们需要采用面向对象的编程方法
- import requests
- from retrying import retry # 怎么利用retry去创造多几次请求的机会
-
-
- class Demo:
- def __init__(self):
- self.url_ = 'xxxx.com'
- # 设置一个记数的变量
- self.num_ = 0
-
- # 发送请求的发送
- @retry(stop_max_attempt_number=3)
- def send_request(self):
- self.num_ += 1
- print(self.num_)
- requests.get(self.url_) # 报错,导致下面的代码执行不到
-
- def run(self):
- try:
- self.send_request()
- except Exception as e:
- print(e)
-
-
- if __name__ == '__main__':
- demo = Demo()
- demo.run()
因为我们要请求的网站是瞎编的,所以请求不会成功,以此我们来测试一下retry功能,retry中有一个参数:stop_max_attempt_number ,最多尝试次数,即我们请求失败时,有多少次重新请求的机会。
异步:多任务可以同时进行,而且互不干扰
加载:页面的渲染
动态:页面没有发生跳转的情况下,里面的数据发生了变化
例子:https://movie.douban.com/chart页面右边电影的分类随便选择一类
当我们向下滑动页面时,页面并没有发生变化,但会有新的电影信息加载出来,这就是典型的异步加载的动态数据
分析:
异步加载:既然有新的数据产生,所以必定发送了相应的请求,在例子中就是向下滑动页面时触发了js代码中的ajax,然后发送了请求,获取了响应
动态数据:这些响应中就有数据,而这些数据我们就称之为动态数据
异步加载中说到发送了相应的请求,那必然是对不同的url进行了访问,html格式的一般都是静态数据,因为他是页面架构;动态数据是之后去填充页面的数据,基本上都是json格式。我们想要电影的信息,他是随着我们不断的向下滑动鼠标才能加载出来,即这些数据为动态数据。我们可以在鼠标右键检查的network中的xhr找到对应的动态数据包(动态数据包一般都可以在xhr中找到),我们在豆瓣电影的例子中发现一个动态数据包中有20条数据(20部电影的信息......),当鼠标向下滑动触发了ajax,就有请求发送,新的数据包就随之产生,然后新的数据包对页面进行填充。当向下滑动后,产生新的数据包,就意味着进行了翻页操作,1个url对应20条数据,当我们需要100条数据时,就得找到5个这样的url,而数据包的产生是类似的,即翻页是类似的,那么他们的url是否会存在一定的规律呢?
我们以豆瓣电影的爱情类电影为例:
- '''
- 第一页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=0&limit=20
- 第二页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=20&limit=20
- 第三页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=40&limit=20
- 第四页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=60&limit=20
- 第五页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=80&limit=20
- '''
我们通过对比分析,发现只有start的值不同,而且每一页之间的差为20,这就是翻页的规律
- import requests
- from fake_useragent import FakeUserAgent
- import time
-
- if __name__ == '__main__':
- # 确认要抓取数据的页面个数
- pages_ = int(input('请输入要抓取数据的页面个数:'))
-
- for page_ in range(pages_):
- # 1.确认目标的url
- url_ = f'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start={20 * page_}&limit=20'
-
- # 构造请求头
- headers_ = {
- 'User-Agent': FakeUserAgent().random
- }
-
- # 2.发送请求
- response_ = requests.get(url_, headers=headers_)
- content_ = response_.content
-
- # 3.解析数据(略)
- # 4.保存数据
- with open(f'豆瓣{page_ + 1}.json', 'wb') as f:
- f.write(content_)
-
- # 降低请求频率,防止被反爬
- time.sleep(1)
我们现在需要五个页面的数据,即需要对页面发送请求,而且他们的url存在规律,每个url中start的值相差20,所以我们可以用一个循环。一到五页的start值为:0,20,40,60,80,range(pages_)的值分别为0,1,2,3,4,所以代码中的start值应该为page_ * 20
Python中一般都是操作字符串类型的数据
json格式数据的提取,解析:需要一个第三方库:jsonpath,re作为辅助
html格式数据的提取,解析:xpath,re作为辅助
爬虫部分:我们从前端拿到(爬到)的json格式的数据,应该怎么转换成为python能够操作的数据呢?
后端部分:我们python提供的数据,怎么转换成为json格式的数据供前端使用呢?
python 转 json 用json中的dumps方法
- import json
-
- if __name__ == '__main__':
- # python中的字典
- python_data = {
- 'name': '小明',
- 'age': '18',
- }
- print(type(python_data), python_data)
- # <class 'dict'> {'name': '小明', 'age': '18'}
-
- # python转json 用dumps函数,如果需要显示中文,需要添加参数
- json_data = json.dumps(python_data)
- print(type(json_data), json_data)
- # <class 'str'> {"name": "\u5c0f\u660e", "age": "18"}
- json_data = json.dumps(python_data, ensure_ascii=False)
- print(type(json_data), json_data)
- # <class 'str'> {"name": "小明", "age": "18"}
- # 如果需要格式更加美观,需要添加一个indent参数
- json_data = json.dumps(python_data, ensure_ascii=False, indent=3)
- print(json_data)
- '''
- {
- "name": "小明",
- "age": "18"
- }
- '''
需要注意的是:
1.我们定义python中字典是用的都是单引号,在转换成json格式后,所有单引号变成了双引号,这是因为json中的引号必须是双引号
2.我们定义python中字典时在最后面加了个逗号,在转换成json格式后,末尾的逗号消失了,这是因为json格式中末尾不能有逗号
3.python转json格式时如果不加参数:ensure_ascii=False,那么python格式中的中文在转化成json格式后会出现乱码
json 转 python 用json中的loads方法
- import json
-
- if __name__ == '__main__':
- # python中的字典
- python_data = {
- 'name': '小明',
- 'age': '18'
- }
- print(type(python_data), python_data)
- # <class 'dict'> {'name': '小明', 'age': '18'}
-
- json_data = json.dumps(python_data, ensure_ascii=False, indent=3)
- print(type(json_data), json_data)
- '''
- <class 'str'>
- {
- "name": "小明",
- "age": "18"
- }
- '''
- # json 转 python 用loads函数
- py_data = json.loads(json_data)
- print(type(py_data), py_data)
- # <class 'dict'> {'name': '小明', 'age': '18'}
如何将python字典写入json文件?
- import json
-
- if __name__ == '__main__':
- file = open('json01.json', 'w')
-
- # 创建一个python字典
- python_dict = {
- 'name': '小明',
- 'age': 18
- }
-
- # 将python字典写入json文件用dump方法
- # 正常显示中文需要添加参数ensure_ascii=False,格式好看需要加入一个参数indent
- json.dump(python_dict, file, ensure_ascii=False, indent=3)
如何将json文件读入python转化为字典格式?
- import json
-
- if __name__ == '__main__':
- file = open('json01.json', 'r')
-
- # 将json文件读入python转化为字典格式用load
- py_data = json.load(file)
- print(py_data) # {'name': '小明', 'age': 18}
总结:
loads:json字符串 > python
load:json文件 > python
dumps:python > json字符串
dump:python > json文件
非常繁琐,不是很推荐
- import requests
- import time
-
-
- class DouBan:
- def __init__(self):
- """定义静态的属性,url,User-Agent"""
- self.url_ = 'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&limit=20'
- self.headers_ = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
- }
-
- def send_request(self, params_): # url传参
- """发送请求,获取响应"""
- response_ = requests.get(self.url_, headers=self.headers_, params=params_)
- str_data = response_.text
- return str_data
-
- def save_file(self, page, str_data):
- """保存文件"""
- with open(f'{page + 1}.json', 'w', encoding='utf-8') as f:
- f.write(str_data)
-
- def run(self):
- """调度方法"""
- pages = int(input('请输入想要抓取的页数:'))
- for page in range(pages):
- # 定义url的start参数
- params_ = {
- 'start': page * 20
- }
- # 调用发送请求的方法
- data_ = self.send_request(params_)
- # 调用保存的方法
- self.save_file(page, data_)
- # 降低请求频率,避免被反爬
- time.sleep(1)
-
-
- if __name__ == '__main__':
- douban_ = DouBan()
- douban_.run()
网站:json.cn 把json格式的数据复制进去,可以使结构更美观,清晰
使用方法举例:
- import jsonpath
-
- data_ = { "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
- }
- }
- }
-
- # 使用方法
- res_ = jsonpath.jsonpath(data_,'$.store.book[*].author')
JSON路径 | 结果 |
---|---|
$.store.book[*].author | 商店中所有书籍的作者 |
$..author | 所有的作者 |
$.store.* | 商店下所有的元素 |
$.store..price | 商店中所有内容的价格 |
$..book[2] | 第三本书 |
$..book[(@.length-1)] | $..book[-1:] | 一本书 |
$..book[0,1] | $..book[:2] | 前两本书 |
$..book[?(@.isbn)] | 获取有isbn的所有数 |
$..book[?(@.price<10)] | 获取价格大于10的所有的书 |
$..* | 获取所有数据 |
使用方法:jsonpath.jsonpath(对象,jsonpath路径)
使用jsonpath提取到的数据是一个列表
jsonpath的语法并不固定,要取同一个内容,jsonpath路径可能不同
注意:
简单方法中是使用了response的json方法,不是导入的json模块
- # 将得到的json格式的数据转换为python能够操作的格式
- data_ = response_.text
- py_data = json.loads(data_)
-
- # 简单方法
- py_data = response_.json()
实际使用(豆瓣电影):
- import jsonpath
- import requests
- import json
-
- if __name__ == '__main__':
- # 1.确认目标url
- url_ = 'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=0&limit=20'
-
- # 构造用户代理
- headers_ = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
- }
-
- # 2.发送请求,获取响应
- response_ = requests.get(url_, headers=headers_)
- # 转换为python格式
- py_data = response_.json()
-
-
- # 3.解析 title score
- title_list = jsonpath.jsonpath(py_data, '$..title') # 列表
- score_list = jsonpath.jsonpath(py_data, '$..score')
- print(title_list)
- print(score_list)
-
- # 4.保存
- for i in range(len(title_list)): # 一条一条数据写入,,让格式看起来更舒服
- dict_ = {}
- dict_[title_list[i]] = score_list[i]
- # 转成json字符串写入
- json_data = json.dumps(dict_,ensure_ascii=False) + ',\n' # 显示中文 换行
- with open('duoban_03.json', 'a', encoding='utf-8') as f: # 进行追加不覆盖
- f.write(json_data)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。