python爬取Ajax动态加载网页过程解析


Posted in Python onSeptember 05, 2019

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

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":"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,指向新的网页链接)

向二级页面链接发请求得到响应内容,并查看嵌入的JS代码

正则提取真实的二级页面链接

# 爬取二级“假”链接
two_html = requests.get(two_link, headers=headers).text
# 从二级页面的响应中提取真实的链接(此处为JS动态加载跳转的地址)
new_two_link = re.findall(r'window.location.href="(.*?)" rel="external nofollow" rel="external nofollow" ', two_html, re.S)[0]

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

数据库中建立version表,存储爬取的链接

每次执行程序和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="(.*?)" rel="external nofollow" rel="external nofollow" '
    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

特点

右键 -> 查看网页源码中没有具体数据

滚动鼠标滑轮或其他动作时加载

抓取

F12打开控制台,选择XHR异步加载数据包,找到页面动作抓取网络数据包

通过XHR-->Header-->General-->Request URL,获取json文件URL地址

通过XHR-->Header-->Query String Parameters(查询参数)

豆瓣电影数据抓取案例

目标

地址: 豆瓣电影 - 排行榜 - 剧情

https://movie.douban.com/typerank?

type_name=%E5%89%A7%E6%83%85&type=11&interval_id=100:90&action=

目标: 爬取电影名称、电影评分

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地址及目标

URL: 百度搜索腾讯招聘 - 查看工作岗位 https://careers.tencent.com/search.html

目标: 职位名称、工作职责、岗位要求

要求与分析

通过查看网页源码,得知所需数据均为 Ajax 动态加载

通过F12抓取网络数据包,进行分析

一级页面抓取数据: 职位名称

二级页面抓取数据: 工作职责、岗位要求

一级页面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('\r\n', '').replace('\n', '') # 去掉换行
    require = html['Data']['Requirement'] # 工作要求
    require = require.replace('\r\n', '').replace('\n', '') # 去掉换行
    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))

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python发送email的3种方法
Apr 28 Python
python 简单的绘图工具turtle使用详解
Jun 21 Python
python3使用smtplib实现发送邮件功能
May 22 Python
Anaconda下安装mysql-python的包实例
Jun 11 Python
使用11行Python代码盗取了室友的U盘内容
Oct 23 Python
Django实现web端tailf日志文件功能及实例详解
Jul 28 Python
通过Python编写一个简单登录功能过程解析
Sep 04 Python
Python生成随机验证码代码实例解析
Jun 09 Python
Python with语句用法原理详解
Jul 03 Python
Python 测试框架unittest和pytest的优劣
Sep 26 Python
python 如何读、写、解析CSV文件
Mar 03 Python
一篇文章弄懂Python关键字、标识符和变量
Jul 15 Python
python实现静态服务器
Sep 05 #Python
python编写简单端口扫描器
Sep 04 #Python
python 3.6.7实现端口扫描器
Sep 04 #Python
python用线性回归预测股票价格的实现代码
Sep 04 #Python
python多线程扫描端口(线程池)
Sep 04 #Python
Python数据分析模块pandas用法详解
Sep 04 #Python
Python实现TCP探测目标服务路由轨迹的原理与方法详解
Sep 04 #Python
You might like
PHP中Session可能会引起并发问题
2015/06/26 PHP
基于jQuery图片平滑连续滚动插件
2009/04/27 Javascript
JS取request值以及自动执行使用示例
2014/02/24 Javascript
javascript随机之洗牌算法深入分析
2014/06/07 Javascript
javascript实现获取cookie过期时间的变通方法
2014/08/14 Javascript
JavaScript中的setMilliseconds()方法使用详解
2015/06/11 Javascript
jquery实现不包含当前项的选择器实例
2015/06/25 Javascript
基于jQuery实现响应式圆形图片轮播特效
2015/11/25 Javascript
javascript简单进制转换实现方法
2016/11/24 Javascript
JavaScript中localStorage对象存储方式实例分析
2017/01/12 Javascript
微信小程序商城项目之淘宝分类入口(2)
2017/04/17 Javascript
js轮播图的插件化封装详解
2017/07/17 Javascript
angularjs路由传值$routeParams详解
2020/09/05 Javascript
JavaScript 中的 this 工作原理
2018/06/20 Javascript
element-ui中select组件绑定值改变,触发change事件方法
2018/08/24 Javascript
Vue2.0学习系列之项目上线的方法步骤(图文)
2018/09/25 Javascript
react native 仿微信聊天室实例代码
2019/09/17 Javascript
解决vuex数据异步造成初始化的时候没值报错问题
2019/11/13 Javascript
Python语言的12个基础知识点小结
2014/07/10 Python
python对象及面向对象技术详解
2016/07/19 Python
基于Python实现对PDF文件的OCR识别
2016/08/05 Python
python+matplotlib实现动态绘制图片实例代码(交互式绘图)
2018/01/20 Python
Python操作配置文件ini的三种方法讲解
2019/02/22 Python
如何更优雅地写python代码
2019/07/02 Python
使用 Python 在京东上抢口罩的思路详解
2020/02/27 Python
使用keras和tensorflow保存为可部署的pb格式
2020/05/25 Python
Python使用lambda抛出异常实现方法解析
2020/08/20 Python
css3进阶之less实现星空动画的示例代码
2019/09/10 HTML / CSS
html5的新玩法——语音搜索
2013/01/03 HTML / CSS
美国购买和销售礼品卡平台:Raise
2017/01/13 全球购物
init进程的作用
2015/08/20 面试题
信息系统专业个人求职信范文
2013/12/07 职场文书
会计求职自荐信
2014/06/20 职场文书
2014年学校法制宣传日活动总结
2014/11/01 职场文书
2015年领导干部廉洁自律工作总结
2015/05/26 职场文书
巴黎圣母院读书笔记
2015/06/26 职场文书