­

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

  • 2019 年 10 月 3 日
  • 筆記

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

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