Python爬虫(第四周)

目录

一、Session保持登录

二、代理IP的使用

三、timeout参数

四、retrying模块的使用

五、异步加载动态数据

六、爬虫中数据的分类以及解析

七、json格式数据

八、面向对象改写案例

九、json数据的解析 > jsonpath的使用


一、Session保持登录

 在上节我们讲到了人人网的案例,发送携带表单数据的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)

二、代理IP的使用

构造方法与请求头的构造方法一致,也是一个键值对

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就全是有效的了。

三、timeout参数

当我们访问网站的时候,会出现响应时间过长的情况。

例如:我们在测试代理IP有效性的时候,即使有些代理IP有效,但存在着访问网站获取响应速度过慢的情况,所以我们需要把这样的IP也剔除掉

response_ = requests.get(url_, headers=headers_, proxies=proxy_, timeout=3)

我们在发送请求时加上timeout参数就可以了,例子中 timeout=3 ,就是在3秒内没有获得响应的话就立即报错

四、retrying模块的使用

见名知意,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格式数据

爬虫部分:我们从前端拿到(爬到)的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数据的解析 > jsonpath的使用

网站: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)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不懂编程的大学生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值