• 爬虫——爬取Ajax动态加载网页


    常见的反爬机制及处理方式

    1、Headers反爬虫 :Cookie、Referer、User-Agent

      解决方案: 通过F12获取headers,传给requests.get()方法

    2、IP限制 :网站根据IP地址访问频率进行反爬,短时间内进制IP访问

      解决方案:

           1、构造自己IP代理池,每次访问随机选择代理,经常更新代理池

           2、购买开放代理或私密代理IP

           3、降低爬取的速度

    3、User-Agent限制 :类似于IP限制

      解决方案: 构造自己的User-Agent池,每次访问随机选择

    5、对查询参数或Form表单数据认证(salt、sign)

      解决方案: 找到JS文件,分析JS处理方法,用Python按同样方式处理

    6、对响应内容做处理

      解决方案: 打印并查看响应内容,用xpath或正则做处理

    python中正则处理headers和formdata

    1、pycharm进入方法 :Ctrl + r ,选中 Regex

    2、处理headers和formdata

    (.*): (.*)

     "$1": "$2",

    3、点击 Replace All

    民政部网站数据抓取

    目标: 抓取最新中华人民共和国县以上行政区划代码

    URL: http://www.mca.gov.cn/article/sj/xzqh/2019/ - 民政数据 - 行政区划代码

    实现步骤

    1、从民政数据网站中提取最新行政区划代码链接

      最新的在上面,命名格式: 2019年X月中华人民共和国县以上行政区划代码

    import requests
    from lxml import etree
    import re
    ​
    url = 'http://www.mca.gov.cn/article/sj/xzqh/2019/'
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'}
    html = requests.get(url, headers=headers).text
    parse_html = etree.HTML(html)
    article_list = parse_html.xpath('//a[@class="artitlelist"]')
    ​
    for article in article_list:
        title = article.xpath('./@title')[0]
        # 正则匹配title中包含这个字符串的链接
        if title.endswith('代码'):
            # 获取到第1个就停止即可,第1个永远是最新的链接
            two_link = 'http://www.mca.gov.cn' + article.xpath('./@href')[0]
            print(two_link)
            break

    2、从二级页面链接中提取真实链接(反爬-响应网页内容中嵌入JS,指向新的网页链接)

    1. 向二级页面链接发请求得到响应内容,并查看嵌入的JS代码
    2. 正则提取真实的二级页面链接
    # 爬取二级“假”链接
    two_html = requests.get(two_link, headers=headers).text # 从二级页面的响应中提取真实的链接(此处为JS动态加载跳转的地址) new_two_link = re.findall(r'window.location.href="(.*?)"', two_html, re.S)[0]

    3、在数据库表中查询此条链接是否已经爬取,建立增量爬虫

    1. 数据库中建立version表,存储爬取的链接
    2. 每次执行程序和version表中记录核对,查看是否已经爬取过
    cursor.execute('select * from version')
    result = self.cursor.fetchall()
    if result:
       if result[-1][0] == two_link:
           print('已是最新')
       else:
           # 有更新,开始抓取
           # 将链接再重新插入version表记录

    4、代码实现

    import requests
    from lxml import etree
    import re
    import pymysql
    
    
    class GovementSpider(object):
        def __init__(self):
            self.url = 'http://www.mca.gov.cn/article/sj/xzqh/2019/'
            self.headers = {'User-Agent': 'Mozilla/5.0'}
            # 创建2个对象
            self.db = pymysql.connect('127.0.0.1', 'root', '123456', 'govdb', charset='utf8')
            self.cursor = self.db.cursor()
    
        # 获取假链接
        def get_false_link(self):
            html = requests.get(url=self.url, headers=self.headers).text
            # 此处隐藏了真实的二级页面的url链接,真实的在假的响应网页中,通过js脚本生成,
            # 假的链接在网页中可以访问,但是爬取到的内容却不是我们想要的
            parse_html = etree.HTML(html)
            a_list = parse_html.xpath('//a[@class="artitlelist"]')
            for a in a_list:
                # get()方法:获取某个属性的值
                title = a.get('title')
                if title.endswith('代码'):
                    # 获取到第1个就停止即可,第1个永远是最新的链接
                    false_link = 'http://www.mca.gov.cn' + a.get('href')
                    print("二级“假”链接的网址为", false_link)
                    break
            # 提取真链接
            self.incr_spider(false_link)
    
        # 增量爬取函数
        def incr_spider(self, false_link):
            self.cursor.execute('select url from version where url=%s', [false_link])
            # fetchall: (('http://xxxx.html',),)
            result = self.cursor.fetchall()
    
            # not result:代表数据库version表中无数据
            if not result:
                self.get_true_link(false_link)
                # 可选操作: 数据库version表中只保留最新1条数据
                self.cursor.execute("delete from version")
    
                # 把爬取后的url插入到version表中
                self.cursor.execute('insert into version values(%s)', [false_link])
                self.db.commit()
            else:
                print('数据已是最新,无须爬取')
    
        # 获取真链接
        def get_true_link(self, false_link):
            # 先获取假链接的响应,然后根据响应获取真链接
            html = requests.get(url=false_link, headers=self.headers).text
            # 从二级页面的响应中提取真实的链接(此处为JS动态加载跳转的地址)
            re_bds = r'window.location.href="(.*?)"'
            pattern = re.compile(re_bds, re.S)
            true_link = pattern.findall(html)[0]
    
            self.save_data(true_link)  # 提取真链接的数据
    
        # 用xpath直接提取数据
        def save_data(self, true_link):
            html = requests.get(url=true_link, headers=self.headers).text
    
            # 基准xpath,提取每个信息的节点列表对象
            parse_html = etree.HTML(html)
            tr_list = parse_html.xpath('//tr[@height="19"]')
            for tr in tr_list:
                code = tr.xpath('./td[2]/text()')[0].strip()  # 行政区划代码
                name = tr.xpath('./td[3]/text()')[0].strip()  # 单位名称
    
                print(name, code)
    
        # 主函数
        def main(self):
            self.get_false_link()
    
    
    if __name__ == '__main__':
        spider = GovementSpider()
        spider.main()

    动态加载数据抓取-Ajax

    特点

    1. 右键 -> 查看网页源码中没有具体数据
    2. 滚动鼠标滑轮或其他动作时加载

    抓取

    1. F12打开控制台,选择XHR异步加载数据包,找到页面动作抓取网络数据包
    2. 通过XHR-->Header-->General-->Request URL,获取json文件URL地址
    3. 通过XHR-->Header-->Query String Parameters(查询参数)

    豆瓣电影数据抓取案例

    目标

    1. 地址: 豆瓣电影 - 排行榜 - 剧情
    2. 目标: 爬取电影名称、电影评分

    F12抓包(XHR)

    1、Request URL(基准URL地址) :https://movie.douban.com/j/chart/top_list?

    2、Query String Paramaters(查询参数)

    # 查询参数如下:
    type: 13 # 电影类型
    interval_id: 100:90
    action: '[{},{},{}]'
    start: 0  # 每次加载电影的起始索引值
    limit: 20 # 每次加载的电影数量

    json文件在以下地址:

    基准URL地址+查询参数

    'https://movie.douban.com/j/chart/top_list?'+'type=11&interval_id=100%3A90&action=&start=20&limit=20'

    代码实现

    import requests
    import time
    from fake_useragent import UserAgent
    
    
    class DoubanSpider(object):
        def __init__(self):
            self.base_url = 'https://movie.douban.com/j/chart/top_list?'
            self.i = 0
    
        def get_html(self, params):
            headers = {'User-Agent': UserAgent().random}
            res = requests.get(url=self.base_url, params=params, headers=headers)
            res.encoding = 'utf-8'
            html = res.json()  # 将json格式的字符串转为python数据类型
            self.parse_html(html)  # 直接调用解析函数
    
        def parse_html(self, html):
            # html: [{电影1信息},{电影2信息},{}]
            item = {}
            for one in html:
                item['name'] = one['title']  # 电影名
                item['score'] = one['score']  # 评分
                item['time'] = one['release_date']  # 打印测试
                # 打印显示
                print(item)
                self.i += 1
    
        # 获取电影总数
        def get_total(self, typ):
            # 异步动态加载的数据 都可以在XHR数据抓包
            url = 'https://movie.douban.com/j/chart/top_list_count?type={}&interval_id=100%3A90'.format(typ)
            ua = UserAgent()
            html = requests.get(url=url, headers={'User-Agent': ua.random}).json()
            total = html['total']
    
            return total
    
        def main(self):
            typ = input('请输入电影类型(剧情|喜剧|动作):')
            typ_dict = {'剧情': '11', '喜剧': '24', '动作': '5'}
            typ = typ_dict[typ]
            total = self.get_total(typ)  # 获取该类型电影总数量
    
            for page in range(0, int(total), 20):
                params = {
                    'type': typ,
                    'interval_id': '100:90',
                    'action': '',
                    'start': str(page),
                    'limit': '20'}
                self.get_html(params)
                time.sleep(1)
            print('爬取的电影的数量:', self.i)
    
    
    if __name__ == '__main__':
        spider = DoubanSpider()
        spider.main()

    腾讯招聘数据抓取(Ajax)

    确定URL地址及目标

    要求与分析

    1. 通过查看网页源码,得知所需数据均为 Ajax 动态加载
    2. 通过F12抓取网络数据包,进行分析
    3. 一级页面抓取数据: 职位名称
    4. 二级页面抓取数据: 工作职责、岗位要求

    一级页面json地址(pageIndex在变,timestamp未检查)

    https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn

    二级页面地址(postId在变,在一级页面中可拿到)

    https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn

    useragents.py文件

    ua_list = [
      'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1',
      'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0',
      'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)',
    ]
    import time
    import json
    import random
    import requests
    from useragents import ua_list
    
    
    class TencentSpider(object):
        def __init__(self):
            self.one_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
            self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn'
            self.f = open('tencent.json', 'a')  # 打开文件
            self.item_list = []  # 存放抓取的item字典数据
    
        # 获取响应内容函数
        def get_page(self, url):
            headers = {'User-Agent': random.choice(ua_list)}
            html = requests.get(url=url, headers=headers).text
            html = json.loads(html)  # json格式字符串转为Python数据类型
    
            return html
    
        # 主线函数: 获取所有数据
        def parse_page(self, one_url):
            html = self.get_page(one_url)
            item = {}
            for job in html['Data']['Posts']:
                item['name'] = job['RecruitPostName']  # 名称
                post_id = job['PostId']  # postId,拿postid为了拼接二级页面地址
                # 拼接二级地址,获取职责和要求
                two_url = self.two_url.format(post_id)
                item['duty'], item['require'] = self.parse_two_page(two_url)
                print(item)
                self.item_list.append(item)  # 添加到大列表中
    
        # 解析二级页面函数
        def parse_two_page(self, two_url):
            html = self.get_page(two_url)
            duty = html['Data']['Responsibility']  # 工作责任
            duty = duty.replace('
    ', '').replace('
    ', '')  # 去掉换行
            require = html['Data']['Requirement']  # 工作要求
            require = require.replace('
    ', '').replace('
    ', '')  # 去掉换行
    
            return duty, require
    
        # 获取总页数
        def get_numbers(self):
            url = self.one_url.format(1)
            html = self.get_page(url)
            numbers = int(html['Data']['Count']) // 10 + 1  # 每页有10个推荐
    
            return numbers
    
        def main(self):
            number = self.get_numbers()
            for page in range(1, 3):
                one_url = self.one_url.format(page)
                self.parse_page(one_url)
    
            # 保存到本地json文件:json.dump
            json.dump(self.item_list, self.f, ensure_ascii=False)
            self.f.close()
    
    
    if __name__ == '__main__':
        start = time.time()
        spider = TencentSpider()
        spider.main()
        end = time.time()
        print('执行时间:%.2f' % (end - start))
  • 相关阅读:
    通过Web启动本地应用程序
    类似百度文库文档预览方式实现
    cas4.0 session中返回更多的用户信息
    word转pdf图片问题
    JSON数据转换成table表格
    2017年各银行卡跨行取款收费标准
    解决win8/8.1系统安装.net framework 3.5出现0x800F0906代码错误
    更改Thunderbird的默认语言
    java获取客户端ip地址
    为jquery ajax请求增加正在运行提示
  • 原文地址:https://www.cnblogs.com/LXP-Never/p/11374795.html
Copyright © 2020-2023  润新知