zoukankan      html  css  js  c++  java
  • 爬虫之requests模块的使用

    requests模块

    • 概念:基于网络请求的模块
    • 作用:用来模拟浏览器发请求,从而实现爬虫
    • 环境安装:pip install requests
    • 编码流程:
      • 指定url
      • 发起请求
      • 获取响应数据
      • 持久化存储

    示例:

    1:爬取搜狗首页的页面源码数据

    import requests
    #1.指定url
    url = 'https://www.sogou.com/'
    #2.请求发送:get返回的是一个响应对象
    response = requests.get(url=url)
    #3.获取响应数据:text返回的是字符串形式的响应数据
    page_text = response.text
    #4.持久化存储
    with open('sogou.html','w',encoding='utf-8') as fp:
        fp.write(page_text)
    

    2:实现一个简易的网页采集器(请求参数的动态化)

    url = 'https://www.sogou.com/web'
    query = input('请输入参数:')
    params = {
        'query': query
    }
    response = requests.get(url=url, params=params)
    file_name = query + '.html'
    page_text = response.text
    with open(file_name, 'w', encoding='utf-8') as fp:
        fp.write(page_text)
    

    上述代码问题:

    • 乱码问题
      • response.encoding = 'xxx'
    • 数据丢失
      • 反爬机制: UA(User-Agent)检测
      • 反反爬策略: UA伪装
    # 改进上述代码
    url = 'https://www.sogou.com/web'
    query = input('请输入参数:')
    params = {
        'query': query
    }
    # UA伪装
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
    }
    response = requests.get(url=url, params=params, headers=headers)
    # 指定编码格式
    response.encoding = 'utf-8'
    file_name = query + '.html'
    page_text = response.text
    with open(file_name, 'w', encoding='utf-8') as fp:
        fp.write(page_text)
    

    3:动态加载的数据

    • 通过另一个网络请求(例如ajax)请求到的数据

    • 爬取豆瓣电影中动态加载出的电影详情数据

    url = 'https://movie.douban.com/j/chart/top_list'
    params = {
        'type': '5',
        'interval_id': '100:90',
        'action': '',
        'start': '0',
        'limit': '10'
    }
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
    }
    response = requests.get(url=url, params=params, headers=headers)
    # 在已知响应数据是json字符串时,可以使用json()直接获得反序列化的原数据
    movie_list = response.json()
    for movie in movie_list:
        print(movie['title'], movie['score'])
    

    总结: 对一个陌生的网站进行数据爬取的时候,首先要确定的一点就是爬取的数据是否为动态加载出来的

    • 是: 需要通过抓包工具捕获到动态加载数据对应的数据包,从中提取出url和请求参数
    • 不是: 直接对浏览器地址栏的url发起请求即可

    如何检测爬取的数据是不是动态加载出来的?

    • 通过抓包工具进行局部搜索(response)就可以验证数据是否为动态加载
      • 搜索到: 不是动态加载
      • 搜索不到: 是动态加载

    如何定位动态加载的数据在哪呢?

    • 通过抓包工具进行全局搜索进行定位

    4:爬取肯德基餐厅位置信息

    url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
    }
    data = {
        'cname': '',
        'pid': '',
        'keyword': '北京',
        'pageIndex': '1',
        'pageSize': '10'
    }
    response = requests.post(url=url, data=data, headers=headers)
    address_dic = response.json()
    for address in address_dic['Table1']:
        print(address['cityName'], address['addressDetail'])
    

    5: 需求 https://www.fjggfw.gov.cn/Website/JYXXNew.aspx 福建省公共资源交易中心,提取完整的html中标信息

    实现思路

    • 确认爬取的数据都是动态加载出来的
    • 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
    • 对提取到的url进行请求发送,获取响应数据(json)
    • 从json串中提取到每一个公告对应的id值
    • 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
    post_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx'
    # 此处用到了cookie
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
        'Cookie': '_qddac=4-3-1.4euvh3.dolhcp.k1hv0g18; ASP.NET_SessionId=zlodpss0z5marc42xbwf3k1z; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570540077; __root_domain_v=.fjggfw.gov.cn; _qddaz=QD.xezyl8.p60887.k1hv0fxc; _qdda=4-1.4euvh3; _qddab=4-dolhcp.k1hv0g18; _qddamta_2852155767=4-0; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570540248; _qddagsx_02095bad0b=01ea1a6c5d3a64853ca5827a992c7e8755a27bdf6483c4170cf2f7408bf5160b8be84faf079220f53eb77ffddb8e7a31bb676d8e2335aa55f11f4fd4ea8e3ae123c0a5f18a8ab6b832b0d1b4888af4bdd0787e3a2fbda9234cb86cd2b05adf3e56d7e29aafcb05c7edc7e73de6cb346d19449446dc77234a6fb176cd0c0e4df4'
    }
    for n in range(1, 6):
        data = {
            'OPtype': 'GetListNew',
            'pageNo': n,
            'pageSize': '10',
            'proArea': '-1',
            'category': 'GCJS',
            'announcementType': '-1',
            'ProType': '-1',
            'xmlx': '-1',
            'projectName': '',
            'TopTime': '2019-07-10 00:00:00',
            'EndTime': '2019-10-08 23:59:59',
            'rrr': '0.5270491290780797'
        }
        post_data = requests.post(url=post_url, data=data, headers=headers).json()
        for i in post_data['data']:
            mid = int(i['M_ID'])
            url = f'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx?OPtype=GetGGInfoPC&ID={mid}&GGTYPE=5&url=AjaxHandler%2FBuilderHandler.ashx'
            response = requests.get(url=url, headers=headers)
            response.encoding = 'utf-8'
            data_dic = response.json()
            with open('作业.text', 'a', encoding='utf-8') as f1:
                f1.write(''.join(data_dic['data']))
                f1.write('
    ----------------------------')
    

    6: 爬取图片

    • 基于requests
    • 基于urllib
    • 区别: urllib中的urlretrieve不可以进行UA伪装
    # 基于requests模块的图片爬取
    import requests
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
    }
    url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
    # content返回的是bytes类型的响应数据
    img_data = requests.get(url, headers=headers).content
    with open('ceshi.jpg', 'wb') as f1:
        f1.write(img_data)
    
    # 基于urllib的图片爬取
    from urllib import request
    url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
    request.urlretrieve(url, 'ceshi2.jpg')
    

    7: 反爬之图片懒加载

    需求: 爬取http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html 网站的图片

    import requests
    import os
    from lxml import etree
    
    # http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html 网站中前5页的图片数据进行爬取和持久化存储
    
    file_path = './作业/'
    if not os.path.exists(file_path):
        os.mkdir(file_path)
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
    }
    url = 'http://sc.chinaz.com/tag_tupian/yazhoumeinv_%d.html'
    for page in range(1, 6):
        if page == 1:
            new_url = 'http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html'
        else:
            new_url = url % page
        page_text = requests.get(new_url, headers=headers).text
        tree = etree.HTML(page_text)
        div_list = tree.xpath('//div[@id="container"]/div')
        for div in div_list:
            title = div.xpath('./p/a/text()')[0].encode('iso-8859-1').decode('utf-8')
            img_path = file_path + '/' + title + '.jpg'
            img_url = div.xpath('./div/a/img/@src2')[0]
            img_data = requests.get(img_url, headers=headers).content
            with open(img_path, 'wb') as f1:
                f1.write(img_data)
        print('第{}页爬取完毕~~'.format(page))
    

    反爬机制之: 图片懒加载

    • 使用伪属性记录图片地址
    • 当图片进入浏览器可视窗口时,才通过JS使其加载出来(将伪属性修改为src

    对应的反反爬策略:

    • 爬取伪属性所对应的属性

    8: 爬取梨视频(JS动态加载视频地址)

    import requests
    import os
    import re
    from lxml import etree
    
    # 梨视频短视频的爬取
    
    file_path = './作业3/'
    if not os.path.exists(file_path):
        os.mkdir(file_path)
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
    }
    url = 'https://www.pearvideo.com/category_59'
    page_text = requests.get(url, headers=headers).text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//li[@class="categoryem "]')
    for li in li_list:
        title = li.xpath('./div/a/div[2]/text()')[0]
        movie_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
        movie_text = requests.get(movie_url, headers=headers).text
        src = re.findall('srcUrl="(.*?)",vdoUrl', movie_text, re.S)[0]
        movie_data = requests.get(src, headers=headers).content
        movie_path = file_path + title + '.' + src.split('.')[-1]
        with open(movie_path, 'wb') as f1:
            f1.write(movie_data)
        print('好了一个~')
    print('全部爬取完毕~')
    

    反爬机制之:

    • JS动态加载视频地址

    对应的反反爬策略:

    • 使用正则匹配到JS代码中的视频地址

    9: 反爬之IP检测(代理)

    出现HttpConnectionPool(host:XX) Max retries exceeded with url错误
    - 产生原因:
        - 1.短时间内对服务器端发起了高频请求
            - 处理: headers中加入Connection: 'close'
        - 2.请求对应的ip被服务器端禁止
            - 使用代理
    反爬机制之: 
        - IP异常检测
    对应的反反爬策略: 
        - 使用代理
    
    代理操作
        - 概念: 代理服务器
    代理的作用?
        - 请求和响应的转发(拦截请求和响应)
    代理和爬虫之间的关联是什么?
        - 可以基于代理实现更换爬虫程序请求的ip地址
    代理ip的网站
        - 西刺
        - 快代理
        - www.goubanjia.com
        - 代理精灵: http://http.zhiliandaili.cn/
    代理的匿名度
        - 高匿
        - 匿名
        - 透明
    类型
        - http
        - https
    

    示例1:

    # 使用代理的示例
    import requests
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
        'Connection': 'close'
    }
    url = 'https://www.baidu.com/s?ie=UTF-8&wd=ip'
    page_text = requests.get(url, headers=headers, proxies={'https': '182.85.41.159:41729'}).text
    with open('./代理测试.html', 'w', encoding='utf-8') as f1:
        f1.write(page_text)
    

    示例2:

    # 搭建一个免费的代理池
    # 爬取西刺代理
    url = 'https://www.xicidaili.com/nn/{}'
    ip_list = []
    for page in range(1, 51):
        new_url = url.format(page)
        page_text = requests.get(new_url, headers=headers).text
        tree = etree.HTML(page_text)
        # 注意,xpath中不能出现tbody标签
        tr_list = tree.xpath('//table//tr')[1:]
        for tr in tr_list:
            dic = {}
            dic['ip_port'] = tr.xpath('./td[2]/text()')
            dic['agreement'] = tr.xpath('./td[6]/text()')
            ip_list.append(dic)
    print(len(ip_list))
    
    # 多次爬取后发现,ip被服务器端封禁
    # 使用代理进行反反爬
    

    示例3:

    # 构建一个付费的代理池
    import random
    
    url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=2&fa=0&fetch_key=&groupid=0&qty=50&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=15'
    page_text = requests.get(url, headers=headers).text
    tree = etree.HTML(page_text)
    ip_list = tree.xpath('//body//text()')
    ips_pool = []
    for ip in ip_list:
        ips_pool.append({'https': ip})
    
    
    url = 'https://www.xicidaili.com/nn/{}'
    ip_list = []
    for page in range(1, 51):
        new_url = url.format(page)
        try:
            page_text = requests.get(new_url, headers=headers, proxies=random.choice(ips_pool)).text
            tree = etree.HTML(page_text)
            # 注意,xpath表达式中不能出现tbody标签
            tr_list = tree.xpath('//table//tr')[1:]
            for tr in tr_list:
                dic = {}
                dic['ip_port'] = tr.xpath('./td[2]/text()')
                dic['agreement'] = tr.xpath('./td[6]/text()')
                ip_list.append(dic)
        except Exception:
            pass
    print(len(ip_list))
    

    10: 反爬之cookie

    需求:
        - 爬取www.xueqiu.com 中的新闻数据
    爬虫中处理cookie的操作
        - 手动处理: 将cookie写在headers中
        - 自动处理: session对象
            - 获取session对象: requests.Session()
            - 作用:
                - session对象和requests对象都可以对指定的url进行请求发送,只不过使用session对象进行请求发送如果产生了cookie,则cookie会被自动保存在session对象中.
    
    url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352414&count=15&category=-1'
    page_text = requests.get(url, headers=headers).json()
    #  'error_description': '遇到错误,请刷新页面或者重新登录帐号后再试',
    
    # 基于cookie操作的修正
    session = requests.Session()
    cookie_url = 'https://xueqiu.com'
    # 首先使用session对象向https://xueqiu.com发送一次请求,记录产生的cookie
    session.get(cookie_url, headers=headers)
    url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352414&count=15&category=-1'
    # 保证该次请求携带对应的cookie才可以请求成功
    page_text = session.get(url, headers=headers).json()
    print(page_text)
    

    11: 模拟登陆&验证码的识别&动态请求参数

    使用线上的打码平台进行自动的识别验证码
        - 云打码
        - 超级鹰
            - 注册,登录
            - 创建一个软件
            - 下载示例代码(开发文档中)
    

    开发文档:

    import requests
    from hashlib import md5
    
    class Chaojiying_Client(object):
    
        def __init__(self, username, password, soft_id):
            self.username = username
            password =  password.encode('utf8')
            self.password = md5(password).hexdigest()
            self.soft_id = soft_id
            self.base_params = {
                'user': self.username,
                'pass2': self.password,
                'softid': self.soft_id,
            }
            self.headers = {
                'Connection': 'Keep-Alive',
                'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
            }
    
        def PostPic(self, im, codetype):
            """
            im: 图片字节
            codetype: 题目类型 参考 http://www.chaojiying.com/price.html
            """
            params = {
                'codetype': codetype,
            }
            params.update(self.base_params)
            files = {'userfile': ('ccc.jpg', im)}
            r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
            return r.json()
    
        def ReportError(self, im_id):
            """
            im_id:报错题目的图片ID
            """
            params = {
                'id': im_id,
            }
            params.update(self.base_params)
            r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
            return r.json()
    

    获取验证码:

    # 获取验证码
    def get_code(img_path, img_type):
        chaojiying = Chaojiying_Client('账号', '密码', '软件id')
        im = open(img_path, 'rb').read()
        return chaojiying.PostPic(im, img_type)['pic_str']
    

    爬取代码:

    from lxml import etree
    login_url = 'https://so.gushiwen.org/user/login.aspx'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
        'Connection': 'close'
    }
    session = requests.Session()
    login_text = session.get(login_url, headers=headers).text
    tree = etree.HTML(login_text)
    # 验证码的识别: 将验证码下载到本地然后提交给打码平台进行识别
    # 事后发现: cookie是在获取验证码图片时产生的
    code_img_url = 'https://so.gushiwen.org' + tree.xpath('//*[@id="imgCode"]/@src')[0]
    code_img_data = session.get(code_img_url, headers=headers).content
    with open('./验证码.jpg', 'wb') as f1:
        f1.write(code_img_data)
    code = get_code('./验证码.jpg', 1004)
    # 获取动态的请求参数
    __VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
    __VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]
    data = {
        '__VIEWSTATE': __VIEWSTATE,
        '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
        'from': 'http://so.gushiwen.org/user/collect.aspx',
        'email': '账号',
        'pwd': '密码',
        'code': code,
        'denglu': '登录'
    }
    page_text = session.post(login_url, data=data, headers=headers).text
    with open('./测试.html', 'w', encoding='utf-8') as f1:
        f1.write(page_text)
    

    如何捕获动态变化的请求参数

    • 通常情况下,动态变化的请求参数都会被隐藏在前台页面源码数据中
    目前接触到的反爬机制及反反爬策略:
        - robots: 不管它
        - UA检测: UA伪装
        - 图片懒加载: 使用伪属性获取图片地址
        - IP检测(代理): 使用代理
        - cookie: 使用session对象
        - 验证码: 使用线上打码平台解析验证码
        - 动态变化的请求参数: 从页面源码中解析动态变化的请求参数
        - 动态加载的数据: 使用抓包工具,全局搜索,定位到动态加载的数据的位置
    

    12: 使用线程池提升爬取数据的效率

    同步操作的代码:

    import time
    # 同步操作的代码
    def request(url):
        print('正在请求:', url)
        time.sleep(2)
        print('请求完毕:', url)
    urls = [
        'www.1.com',
        'www.2.com',
        'www.3.com'
    ]
    
    start = time.time()
    for url in urls:
        request(url)
    print('总耗时:', time.time() - start)
    # 6秒
    

    基于线程池的异步操作代码:

    import time
    from multiprocessing.dummy import Pool # 线程池
    
    # 基于线程池的异步操作代码
    pool = Pool(3)
    
    def request(url):
        print('正在请求:', url)
        time.sleep(2)
        print('请求完毕:', url)
        
    urls = [
        'www.1.com',
        'www.2.com',
        'www.3.com'
    ]
    
    start = time.time()
    pool.map(request, urls)
    print('总耗时:', time.time() - start)
    # 2秒
    

    爬虫加线程池:

    # 爬虫加线程池
    import time
    import requests
    from multiprocessing.dummy import Pool # 线程池
    from lxml import etree
    urls = [
        'http://127.0.0.1:5000/hxbs',
        'http://127.0.0.1:5000/index'
    ]
    # 发送请求
    def get_request(url):
        page_text = requests.get(url).text
        return page_text
    # 解析数据
    def parse(page_text):
        tree = etree.HTML(page_text)
        print(tree.xpath('//div[1]//text()'))
    pool = Pool(2)
    start = time.time()
    page_text_list = pool.map(get_request, urls)
    print(len(page_text_list))
    pool.map(parse, page_text_list)
    print('总耗时:', time.time() - start)
    
  • 相关阅读:
    Oracle 各种查询语句
    Win7下Eclipse中文字体太小
    ASP.NET MVC(Razor)上运用UEditor和xhEditor编辑器检测到有潜在危险的 Request.Form的真正解决办法
    Oracle 分页
    限制IIS访问流量提升IIS性能
    pl\sql工具导出表结构、序列和触发器方法
    JS 中面向对象的5种写法
    去除Windows 2003的登录CTRL+ALT+DEL
    List绑定时无法进行增删查改的解决办法
    .net工具
  • 原文地址:https://www.cnblogs.com/zyyhxbs/p/11658293.html
Copyright © 2011-2022 走看看