12.利用API抓取数据
随着 JavaScript 和 Ajax 内容的生成和加载变得越来越普遍,解决该问题的方法之一:利用 Selenium 让浏览器自动加载网站并获取数据。
本章完全不用理会 JavaScript(没有必要运行甚至是加载 JavaScript),直接获得数据源:生成数据的 API。
1.API概述
API 定义了允许一个软件与另一个软件通信的标准语法,即便是这两个软件是用不同的语言编写的或者是架构不同。
本篇重点介绍 Web API(特别是允许 Web 服务器与浏览器交流的 API),,在其他上下文中,API 也会被用作一个通用的词,指允许 Java 程序与 Python 程序在同一台机器上通信的接口。API 并不一定是“跨网络的”,也并不总是涉及任何 Web 技术。
API 的文档通常将路由或者端点(endpoint)描述为你可以请求的 URL,而变量参数要么是 URL 路径,要么是 GET 请求的参数。例如以下两个链接pathparam分别作为路由路径里的一个参数,以及作为param1的参数值。(传递变量数据)
http://example.com/the-api-route/pathparam
http://example.com/the-api-route?param1=pathparam
API 的响应通常是 JSON 或者 XML 格式的。很多 API 允许你通过改变一个参数来改变响应类型。两种格式示例如下:
{“user”:{“id”: 123, “name”: “Ryan Mitchell”, “city”: “Boston”}}
<user><id>123</id><name>Ryan Mitchell</name><city>Boston</city></user>
(1)HTTP方法和API
利用 HTTP 从 Web 服务器获取信息有 4 种方式(或方法):
- GET,GET 请求对服务器数据库的信息不会有任何影响。只是读取信息。
- POST,当你填写表单或提交信息到 Web 服务器的后端程序时,使用的就是 POST。用API 发起一个 POST 请求,相当于说“请把这个信息保存到你的数据库里”。
- PUT,PUT 请求用来更新一个对象或信息。例如更新老用户邮箱地址。
- DELETE,DELETE 用于删除对象。DELETE 方法在公共 API 里面不常用,公共 API 主要用于传播信息或者允许用户创建或发布信息,而不是让用户删掉数据库中的信息。
- (技术上看,不止存在以上 4 种方式(例如 HEAD、OPTIONS 和 CONNECT),但很少用,大多数 API 仅提供了以上 4 种方法,甚至是这 4 种方法的一个子集。)
GET 请求不同,除了你请求数据的 URL 或路由以外,POST、PUT 和 DELETE 请求还允许你在请求体中发送其他信息。
和你从 Web 服务器接收到的响应一样,请求体中的这个数据通常也是 JSON 格式的,有时是 XML 格式的,而且数据的格式是在 API 的语法中定义好的。
例如,用一个 API创建博客文章的评论,可能会发送一个 PUT 请求到:
http://example.com/comments?post=123
请求体为:
{“title”: “Great post about APIs!”, “body”: “Very informative. Really helped me out with a tricky technical challenge I was facing. Thanks for taking the time to write such a detailed blog post about PUT requests!”, “author”: {“name”: “Ryan Mitchell”, “website”: “http://pythonscraping.com”, “company”: “O’Reilly Media”}}
这里博客文章的 ID(123)作为参数传入 URL,即你做出的新评论的内容通过请求
体传送。参数和数据可以在参数变量和请求体中同时传送。而需要哪些参数以及在哪里传送依然是由 API 的语法决定的。
(2)更多关于API响应的介绍
API 的一个重要特性是会返回格式良好的响应。最常见的响应格式是 XML(eXtensible Markup Language,可扩展标记语言)和JSON(JavaScript Object Notation,JavaScript 对象表示法)。
JSON比XML受欢迎,一个是因为JSON文件通常比XML文件小。另一个原因是Web技术的改变(过去,服务器端用 PHP和 .NET 这些程序作为 API 的接收端。现在,服务器端也会用一些 JavaScript 框架作为 API的发送和接收端,比如 Angular 或 Backbone 等。像 Backbone 之类的 JavaScript 库处理 JSON 要比处理 XML 简单。)、
API 的响应类型受限于创建它的程序员的想象力。CSV 是另外一种典型的响应输出。一些 API 甚至被设计用来生成文件输出。一个请求可能是要求服务器生成一幅带有特
定文本的图像,或者请求特定的 XLSX 或 PDF 文件。一些API则完全没有响应。
如果API没有很好地进行配置,可能会使不可解析的栈跟踪或普通英文文本,当向 API 发出一个请求时,明智的做法通常是首先检查你得到的响应是 JSON 格式(或者是 XML、CSV 或其他你期望的格式)。
2.解析JSON数据
淘宝IP地址库网站查IP的API,用Python的 JSON 解析函数来解码:
import json
from urllib.request import urlopendef getCountry(ipAddress):response = urlopen('http://ip.taobao.com/service/getIpInfo.php?ip=' + ipAddress).read().decode('urf-8')responseJson = json.loads(response)return responseJson.get('data')['country']print(getCountry('50.78.253.58'))
JSON 解析库是 Python 标准库的一部分。Python 使用了一种更加灵活的方式,将 JSON 对象转换成字典,将 JSON 数组转换成列表,将 JSON 字符串转换成 Python 字符串,等等。
下面代码显示如何使用Python的JSON解析库,处理 JSON 字符串中可能出现的不同数据类型:
import json
jsonString = '{"arrayOfNums":[{"number": 0}, {"number": 1}, {"number": 2}], "arrayOfFruits":[{"fruit": "apple"}, {"fruit": "banana"}, {"fruit": "pear"}]}'
jsonObj = json.loads(jsonString)print(jsonObj.get('arrayOfNums'))
print(jsonObj.get('arrayOfNums')[1])
print(jsonObj.get('arrayOfNums')[1].get('number') + jsonObj.get('arrayOfNums')[2].get('number'))
print(jsonObj.get('arrayOfFruits')[2].get('fruit'))
第一行是一个字典对象列表,第二行是一个字典对象,第三行是一个整数(第一行字典列表中整数的和),第四行是一个字符串。
3.无文档的API
大多数 API 是没有发布任何文档的。这一切都与JavaScript 有关。
通常,在用户请求一个网页时,动态网站的 Web 服务器会做以下几件事情:
- 处理来自请求网站页面的用户的 GET 请求
- 从数据库检索页面需要呈现的数据
- 按照 HTML 模板组织页面数据
- 发送带格式的 HTML 给用户
由于 JavaScript 框架变得越来越普遍,很多 HTML 创建任务从原来的由服务器处理变成了由浏览器处理。服务器可能给用户浏览器发送一个硬编码的 HTML 模板,但是还需要单独的 Ajax 请求来加载内容,并将这些内容放到 HTML 模板中正确的位置(slot)。所有这些都发生在浏览器 / 客户端上。
爬虫抓取的是一个不带有任何内容的 HTML 模板。Selenium让网络爬虫可以变成浏览器,请求HTML 模板,执行任意的 JavaScript,允许加载所有的数据,然后再抓取网页的数据。整个内容管理系统(曾经只位于 Web 服务器中)基本上已经移到了浏览器端,当使用 Selenium 时,用户不需要的“额外信息”也被加载了。
因为服务器不再将数据处理成 HTML 格式,所以它们通常作为数据库本身的一个弱包装器。该弱包装器简单地从数据库中抽取数据,并通过一个 API 将数据返回给页面。这些 API 并未打算供除网页本身以外的任何人或者任何事使用,因此开发者未为这些 API 提供文档。但这些API的确是存在的。
用Selenium加载该页面可能发送近百次请求企鹅传输较多的数据,如果直接使用API的话,你只需发起一次请求。
(1)查找无文档的API
使用 Chrome 检查器查看用于构建页面的调用的请求和响应。Chrome 检查器窗口并点击网络选项卡Network。需要在页面加载前就打开这个窗口。当关闭时,它不会追踪网络调用。
API 调用有几个特征:
- 它们通常包含 JSON 或 XML。你可以利用搜索 / 过滤字段过滤请求列表
- 利用 GET 请求,URL 中会包含一个传递给它们的参数。
- 它们通常是 XHR 类型的。(XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP),尽管可能受到更多出于安全等原因的限制。)
(2)记录未被记录的API
要在网站上加载多个页面,在检查器控制台的网络选项卡中筛选出目标 API 调用。之后你可以看到这个调用在不同页面的变化,并且识别出该调用接收的字段和返回的字段。
API 调用可以留心以下字段进行识别和记录:
- 使用的 HTTP 方法
- 输入。路径参数,请求头(包括cookie),正文内容(对于PUT和POST调用)
- 输出。响应头(包括cookie集合),响应正文类型,响应正文字段
(3)自动查找和记录API
查找和记录 API是一项烦琐和偏算法的工作。有些网站可能尝试混淆浏览器是如何获得数据的。查找和记录 API 主要是一个程序性任务。
本书作者创建的 https://github.com/REMitchell/apiscraper GitHub仓库,试图帮助查找和记录API。该工具会使用 Selenium、ChromeDriver 和一个叫作 BrowserMob Proxy 的库来加载页面,在一个域内抓取网页,分析页面加载过程中的网络流量,并将这些请求组织成可读的 API调用。项目包含以下文件:
- apicall.py,包含定义一个 API 调用的属性(路径、参数等),以及确定两个 API 调用是否相同的逻辑。
- apiFinder.py,一个主抓取类。被 webservice.py 和 consoleservice.py 用来实现查找 API 的过程。
- browser.py,仅有 3 个方法,initialize、get 和 close,但是具有一项比较复杂的功能,即将BrowserMob Proxy 和 Selenium 捆绑在一起。滚动页面以确保整个页面都被加载,将HTTP 存档(HAR)文件保存到合适的位置以便后续处理。
- consoleservice.py,处理来自控制台的命令,并且负责主 APIFinder 类。
- harParser.py,它解析 HAR 文件并抽取 API 调用。
- html_template.html,它提供在浏览器中显示 API 调用的一个模板。
- README.md,Git 的 readme 页面。
https://bmp.lightbody.net/ 下载 BrowserMob Proxy 的二进制文件,并将其解压缩文件放到apiscraper 项目的路径下。二进制文件放在项目根路径下的browsermob-proxy-2.1.4/bin/browsermob-proxy位置。
下载(对应使用浏览器版本) ChromeDriver,并将其放在 apiscraper 项目路径下。
还需要安装这些Python 库:tldextract,selenium,browsermob-proxy(均用pip安装)
以上准备工作做好后,可以开始搜集API调用了。
命令行进入项目文件夹,输入:python consoleservice.py -h
python consoleservice.py -u 目标URL -s 搜索词,运行出现了一些问题:(未完全搞明白,未继续深入了解)
- apiFinder.py文件中需要修改chromedriver.exe的路径,chromedriver.exe文件需要放到Chrome软件chrome.exe所在目录下,并将该目录添加到环境变量的Path中。
- 运行有时候报错:UnicodeEncodeError: ‘gbk’ codec can’t encode character ‘\u2122’ in position。需要在错误提示所在命令行open()函数中加入encoding=‘utf-8’
- 运行后apiscraper项目下的server.log提示“ ‘browsermob-proxy-2.1.4’ 不是内部或外部命令,也不是可运行的程序或批处理文件。”进入项目目录browsermob-proxy-2.1.4/bin/下直接执行browsermob-proxy.bat弹出相应窗口。
利用 -i 标志位,可以从提供的初始 URL 开始抓取多个页面。
所有的抓取数据存储在一个 HAR 文件中,默认放在项目路径下的 /har 文件夹中,而该路径可以通过 -d 标志位进行修改。
如果没有提供 URL,你可以传入包含已抓取的 HAR 文件的路径进行查找和分析。
项目提供了其他功能:去除非必需的参数(去除 GET 或 POST 参数,这些参数并不会影响 API 调用的返回值),多种 API 输出格式(命令行、HTML、JSON),区分指示单独 API 路由的路径参数和只是作为同一个 API 路由的 GET 参数的路径参数。
4.API与其他数据源结合
许多现代 Web 应用存在的理由就是抓取现有的数据,再用更好看的形式展现出来。如果你用 API 作为唯一的数据源,那么你最多就是复制别人数据库里的数据。新颖的方式将两个或多个数据源组合起来,或者把 API 作为一种工具,从全新的视角对抓取到的数据进行解释。
把 API 和网页抓取结合起来:维基百科词条的编辑历史页面,里面是一列编辑记录。用户先登录维基百科再编辑词条,显示用户名。不先登录就对词条进行编辑,显示IP地址。搜集IP地址然后查出这些 IP 地址的地理位置,代码如下:
# -*- coding:GBK -*-
from urllib.request import urlopen
from bs4 import BeautifulSoup
import json
import datetime
import re
import randomrandom.seed(datetime.datetime.now())
def getLinks(articleUrl):html = urlopen('http://en.wikipedia.org()'.format(articleUrl))bs = BeautifulSoup(html, 'html.parser')return bs.find('div', {'id': 'bodyContent'}).findAll('a', href=re.compile('^(/wiki/)((?!:).)*$'))def getHistoryIPs(pageUrl):# 编辑历史页面的URL链接格式是# http://en.wikipedia.org/w/index.php?title=Title_in_URL&action=historypageUrl = pageUrl.replace('/wiki/', '')historyUrl = 'http://en.wikipedia.org/w/index.php?title={}&action=history'.format(pageUrl)print('history url is: {}'.format(historyUrl))html = urlopen(historyUrl)bs = BeautifulSoup(html, 'html.parser')# 找出class属性是"mw-userlink mw-anonuserlink"的链接# 他们用IP地址代替用户名ipAddresses = bs.findAll('a', {'class': 'mw-anonuserlink'})addressList = set()for ipAddress in ipAddresses:addressList.add(ipAddress.get_text())return addressListdef getCountry(ipAddress):try:response = urlopen('http://ip.taobao.com/service/getIpInfo.php?ip={}'.format(ipAdress)).read().decode('urf-8')responseJson = json.loads(response)country = responseJson.get('data')['country']except:return Noneelse:return countrylinks = getLinks('/wiki/Python_(programming_language)')while(len(links) > 0):for link in links:print('-'*20)historyIPs = getHistoryIPs(link.attrs['href'])for historyIP in historyIPs:country = getCountry(historyIP)if country is not None:print('{} is from {}'.format(historyIP, country))newLink = links[random.randint(0, len(links)-1)].attrs['href']links = getLinks(newLink)
函数getHistoryIPs,后者搜索所有 class 属性为 mw-anonuserlink 的链接内容(匿名用户的 IP 地址),返回一个链接列表。
代码用随机的搜索模式来查找词条的编辑历史。首先获取起始词条链接到的所有词条的编辑历史。然后,随机选择一个词条作为起始点,再获取这个页面链接到的所有词条的编辑历史。重复这个过程,直到某个页面不包含其他维基词条的链接为止。
获得了编辑历史的 IP 地址数据,用函数getCountry查询 IP 地址所属的国家。
5.再说一点API
如果希望更多地了解 API 的构建和语法,推荐你阅读 Leonard Richardson、Mike Amundsen 和 Sam Ruby 合著的 RESTful Web APIs。该书针对 Web API 的用法提供了非常全面的理论介绍与实践指导。另外,Mike Amundsen 的精彩视频教学课程“Designing APIs for the Web”,也可以教你创利用API抓取数据建自己的 API。
真正的技巧通常是透彻地认识你眼前的网页,并直接从数据源获取数据。
12.利用API抓取数据相关推荐
- 玩转【斗鱼直播APP】系列之利用青花瓷抓取数据
利用青花瓷抓取数据 青花瓷是什么? 当然这里可不是周董的歌曲, 而是我们用于抓取接口的工具. 安装一下这个软件 百度搜索该工具会找到很多绿色版(破解版的文艺说法). 安装即可, 不再累述. 手机端配置 ...
- 小码哥-玩转【斗鱼直播APP】系列之利用青花瓷抓取数据
利用青花瓷抓取数据 青花瓷是什么? 当然这里可不是周董的歌曲, 而是我们用于抓取接口的工具. 安装一下这个软件 百度搜索该工具会找到很多绿色版(破解版的文艺说法). 安装即可, 不再累述. 手机端配置 ...
- 利用 API 爬取数据,试着爬取 QQ 音乐流行指数榜
网址 QQ音乐流行指数网址:https://y.qq.com/n/yqq/toplist/4.html 结果 平凡天使 - G.E.M. 邓紫棋 少年 - 梦然 我们 - 陈奕迅 你要相信这不是最后一 ...
- python怎么编写wireshark抓的包_使用Wireshark 抓取数据包
Wireshark 是一个网络封包分析软件.网络封包分析软件的功能是获取网络封包,并尽可能显示出最为详细的网络封包资料.Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换. 一 ...
- 【入门-R爬虫抓取数据】文本挖掘之数据爬虫
今天主要介绍一下,文本挖掘的数据获取方式,上一篇很多人在问数据如何获取,今天给大家介绍下数据获取的方式,主要利用爬虫抓取数据.基于,之前对python爬虫没接触过,尝试过用R爬虫,今天就来介绍下,如何 ...
- Java模拟登录并抓取数据
问题: 最近做一个抓取数据的项目,发现网上很多资料不完备,或者按照代码执行不能真实爬取数据,自己特别根据自己的网站进行登录并进行数据爬取. 未登录 登录后,正常抓取数据截图(预期目标数据) 解决办法: ...
- Scrapy 案例一:通过 API 抓取天气预报数据
目录 一.概述 二.案例分析 三.编码实现 四.获取多个 city 的天气信息(简单实现) 一.概述 在一些简单的网站中,可以发现,所有的数据都在网页代码中,然而在实际获取数据的过程中,我们可以发现, ...
- pythonallowpos_利用Python抓取并分析京东商品评论数据
2.1 内容简介 本章主要介绍如何利用Python抓取京东商城商品评论信息,并对这些评论信息进行分析和可视化.下面是要抓取的商品信息,一款女士文胸.这个商品共有红色,黑色和肤色等颜色, 70B到90D ...
- 基于Thinkphp5+phpQuery 网络爬虫抓取数据接口,统一输出接口数据api
TP5_Splider 一个基于Thinkphp5+phpQuery 网络爬虫抓取数据接口 统一输出接口数据api.适合正在学习Vue,AngularJs框架学习 开发demo,需要接口并保证接口不跨 ...
- python抓取数据包_利用python-pypcap抓取带VLAN标签的数据包方法
1.背景介绍 在采用通常的socket抓包方式下,操作系统会自动将收到包的VLAN信息剥离,导致上层应用收到的包不会含有VLAN标签信息.而libpcap虽然是基于socket实现抓包,但在收到数据包 ...
最新文章
- sign函数的功能oracle,Oracle中sign函数和decode函数的使用
- 第二章:2.5 卷积辅助求解方法
- ASIHTTPRequest类库简介和使用说明
- NumPy-快速处理数据--ndarray对象--多维数组的存取、结构体数组存取、内存对齐、Numpy内存结构...
- textbox 和textera 文本框多行后不能拉伸
- 洛谷 P1115 最大子段和
- 求平均数、百分率、最大值、最小值、两个字段相差秒数的SQL
- JS中对数组元素进行增、删、改、查的方法,以及其他方法
- 华为P50系列即将发布:麒麟9000E/9000处理器有戏?
- word分页保存_搞定office丨Word快捷技巧第3弹!
- spring boot 处理自定义注解
- Oracle 归档开启切换和归档日志删除(单实例和RAC)
- ubuntu下MySQL无法启动Couldn't find MySQL server (/usr/bin/mysqld_safe)”
- form表单序列化后的数据转json对象
- 梯度下降优化器小结(RMSProp,Momentum,Adam)
- js 获取图片经纬度及写经纬度
- 格雷希尔GripSeal快速密封接头G70外卡式、滑套式快速连接器型号规格
- 论文里引用专利参考文献怎么写?
- 什么样的CTA策略组合最合理?
- 一个使用LABVIEW将16进制字符串分割转化的办法