1. JavaScript与动态内容
对于同一个url,有时用户抓取的内容与在浏览器中看到的内容不同, 这是因为用户通过程序获取的响应内容都是原始的html数据, 而浏览器中所看到的数据是在html的基础上经过JavaScript进一步处理加工后生成的效果。
比如,简书主页(如下图片)。
当你点击阅读更多时, 页面会加载出新的文章信息。 可是我们查看网页源码中并没有包含这些信息。(篇幅原因, 读者请自行在页面中点击右键查看网页源代码)。这是因为这些页面中用到AJAX技术(Asunchronous JavaScript and XML, 异步JavaScript与XML)。
又比如, 拉勾网中切换页面。
对于之前要访问下一个页面的信息是,需要请求新的url (现在我们也会看到有些网页url中的有一个类似page=页数
的请求参数)。然后获取响应, 重新加载整个页面。而使用Ajax技术来说,不需要对整个页面进行刷新, 只要加载其中一部分数据就可以了(拉勾网就是用到这种技术)。
1.1.AJAX
其中某一本书上说:与其说Ajax是一种“技术”, 不如说是一种“方案”。
我们知道javascript是一种很流行的前端脚本语言。使用javascript调度加载局部信息, 可以看成ajax技术。尴尬的是我们的爬虫不会执行这些javascript代码。
对于使用ajax这种技术的页面,我们对其的爬取有两种方法:
-
逆向工程
通过分析AJAX内容, 观察请求目标、请求内容和请求参数等信息,编写出模拟这样的Javascript请求,最终获取页面信息。
-
直接模拟浏览器环境, 这里用到selenium库, 一个自动化库, 可以模拟浏览器。
网页中的AJAX过程一般简单的可以理解为: 发送请求——获得数据——显示元素的流程。
其中, 在第一步“发送请求”时,客户端主要借助了一个所谓的XMLHttpRequest对象(xhr)。我们使用python发送请求代码如下:
import requests
resp = requests.get(url)
浏览器使用XMLHttpRequest也类似于此。 不过浏览器用的JavaScript语言。通过AJAX技术, 浏览器最终会在XMLHttpRequest的responseText属性中获得相应内容, 常见的响应内容有json、html等。
XMLHttpRequest是一个API,他可以微客户端提供在客户端和服务端之间传输数据的功能。它提供了一个通过URL来获取数据的简单方式, 并且不会使整个页面刷新。 具体XMLHttpRequest的定义可以参考Mozilla给出的说明。
1.1.1.通过逆向工程法爬取携程某酒店常见问答板块的实例
代码1
import requests
import json
"""
爬取携程酒店的常见问题
该页面使用ajax技术
使用 request对其进行简单的爬取
"""
#生成索要爬取的url
urls = ('https://hotels.ctrip.com/Domestic/tool/AjaxHotelFaqLoad.aspx?hotelid=429548¤tPage={}'.format(i) for i in range(1, 3))
def get_content(url):
"""
获取响应内容的函数
:param url:
:return:
"""
#传入url 获取响应
resp = requests.post(url)
#解析json字符串
js= json.loads(resp.text)
# print(js)
# raise Exception('暂停')
return js
def parser_content(js):
askList = js['AskList']
return askList
def spider():
"""
爬取调度函数
:return:
"""
global urls
for url in urls:
js = get_content(url)
askList = parser_content(js)
for i in askList:
print('-问:{0}\n 答{1}'.format(i['AskContentTitle'], i['ReplyList']))
if __name__ == '__main__':
spider()
代码2
相比代码1, 代码2是更好的选择。
import requests
import time
from pymongo import MongoClient
client=MongoClient()
#使用名为ctrip的数据库
db = client['ctrip']
#使用其中的collection表:hotelfaq(酒店常见问题)
collection=db['hotelfaq']
global hotel
global max_page_num
raw_url='https://hotels.ctrip.com/Domestic/tool/AjaxHotelFaqLoad.aspx'
#headers
headers = {
'host':'hotels.ctrip.com',
'referer': 'https://hotels.ctrip.com/hotel/429548.html?masterhotelid=429548&hcityid=1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
}
def get_json(hotel, page):
"""
获取json信息
:param hotel: 酒店的id
:param page: 页码
:return:
"""
params = {
'hotelid':hotel,
'page':page
}
try:
resp = requests.get(raw_url,headers=headers, params=params)
if resp.ok: #成功访问
print(type(resp.json()), type(resp.text), sep='\n')
return resp.json() #返回json数据
except Exception as e:
print('Error here:\t', e)
def json_parser(json):
"""
解析json
:param json:
:return:
"""
if json is not None:
asks_list=json.get('AskList')
if not asks_list:
return None
for ask_item in asks_list:
one_ask = {}
one_ask['id'] = ask_item.get('AskId')
one_ask['hotel'] = hotel
one_ask['createtime'] = ask_item.get('CreateTime')
one_ask['ask'] = ask_item.get('AskContentTitle')
one_ask['reply'] = []
if ask_item.get('ReplyList'):
for reply_item in ask_item.get('ReplyList'):
one_ask['reply'].append(
(reply_item.get('ReplierText'),reply_item.get('ReplyContentTitle'), reply_item.get('ReplyTime'))
)
yield one_ask #使用生成yeild方法
def save_to_mongo(data):
"""
存到数据库
:param data:
:return:
"""
if collection.insert_one(data): #插入一条数据
print('Saving to db!')
def worker(hotel):
max_page_num = int(input('input max page num'))#输入最大页数
for page in range(1, max_page_num+1):
time.sleep(1.5)
print('page now \t{}'.format(page))
raw_json=get_json(hotel,page) #获取原始json数据
res_set=json_parser(raw_json)
for res in res_set:
print(res)
save_to_mongo(res)
if __name__ == '__main__':
hotel = int(input('input hotel id.'))
worker(hotel)
参考
- 吕云翔 张扬 Python网络爬虫实战