2018-02-14新闻内容爬虫【上学时做论文自己爬新闻数据,原谅我自己懒发的图片】资源-CSDN文库https://download.csdn.net/download/liuzhuchen/88878591爬虫过的站点:
1QQ新闻
1,准备爬取滚动新闻页面
2 通过F12 开发工具查找发现,动态获取数据url
3 获取数据格式,
注意:请求页面时,必须加头部信息
4 页面内容解析
5 评论获取
评论页面
评论数据
6 注意
2 新浪
1 准备爬取滚动页面
2 滚动页面类别,只是部分,往后和的url 基本都不更新了
3 动态获取滚动页面数据
4 获取的动态页面新闻条目
5 获取评论内容
3 网易新闻
1 滚动新闻
2 页面内容爬取,
3 获取评论内容
4 南方周未
1 滚动爬取
2 评论内容太少,没看
5 环球网
1 滚动爬取
2 评论太少没看
6
7 中国新闻网
1 滚动新闻
2 获取所有的正文 url
3 评论太少,没看
8 搜狐
1 sohu没有可有滚动新闻页面
2 评论获取
9 央视网
1 滚动页面
2 正则获取正文 url
10 python 执行js 脚本
execjs 方法,尝试可有
1 执行函数
2 js 的JSON数据输出为序列,再转为python JSON
3 JSON-js 包
爬虫过的站点:
- 新浪
- 网易
- 南方周未
- 环球网
- 中国新闻网
- 搜狐
- 央视网
- python 执行js 脚本
1QQ新闻
说明:新闻数据量最不大,有一些评论。到是可以爬以前的历史数据
1,准备爬取滚动新闻页面
http://roll.news.qq.com/
可以选择日期,选择国内,国际,社会三种类型
2 通过F12 开发工具查找发现,动态获取数据url
http://roll.news.qq.com/interface/roll.php?0.7455619115457752&cata=newsgn,newsgj,newssh&site=news&date=&page=1&mode=1&of=json
0.7455619115457752就是一个随机数
cata=newsgn,newsgj,newssh表示先中的3种类型,如果是其他大类,体育或是财经也有其他的小类如下:
site=news 表示所在大类
date= 表示往期回顾日期,不写表示当天向后显示10页,每页50条新闻
page=1表示第几页
mode=1 表示标题模式 还是摘要模式
3 获取数据格式
获取数据是一个字典,可以使用如一代码查看
注意:请求页面时,必须加头部信息
#必须用 referer 和 user_agent 参数
import requests
import chardet
user_agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
referer = "http://news.qq.com/"
#构建页面请求的头部
headers = {'User-Agent':user_agent, "Referer":referer}
#构建页面请求
url ='http://roll.news.qq.com/interface/roll.php?0.7455619115457752&cata=newsgn,newsgj,newssh&site=news&date=&page=1&mode=1&of=json'
response = requests.get(url, headers=headers)
res = chardet.detect(response.content)
response.encoding = res['encoding']
response.text
articleRollDict = json.loads(response.text, encoding=response.encoding)
4 页面内容解析
我使用的是
from bs4 import BeautifulSoup as soup
doc = soup(response.text, 'html5lib')
现在使用,doc.select()是不会标签的多个属性一起查找,比如:
要查找div 标签 class 是 box 并且id是 J_Post。
5 评论获取
评论页面
# http://coral.qq.com/+cmt_id
# http://coral.qq.com/2416704416
cmt_id是从内容页面里获取的。
查找如下一段script 代码
<script> |
评论数据
v1版本
使用时可以改成,从最新到旧,注意 有一个参数 V2加 上和不加上是有区别的
腾讯网
%s 是cmt_id值
评论信息数据
{
"errCode":0,
"data":{
"targetid":2416849772,
"display":1,
"total":2236,
"reqnum":1,
"retnum":1,
"maxid":"6368409946459638057",
"first":"6368070716583592288",
"last":"6368070716583592288",
"hasnext":true,
"commentid":[{"id":"6368070716583592288","rootid":"0","targetid":2416849772,"parent":"0","timeDifference":"\u6628\u5929 20:39:31","time":1518266371,"content":"\u5c0a\u656c\u7684\u4e60\u4e3b\u5e2d\u7956\u56fd\u8fd9\u51e0\u5e74\u8de8\u8d8a\u5f0f\u7684\u53d1\u5c55\uff0c\u4f60\u505a\u51fa\u4e86\u4f1f\u5927\u7684\u8d21\u732e\uff0c\u5c24\u5176\u662f\u53cd\u8150\u8d25\u548c\u8001\u767e\u59d3\u7684\u8131\u8d2b\uff0c\u60a8\u662f\u4eba\u6c11\u7684\u529f\u81e3","title":"","up":"16","rep":"0","type":"1","hotscale":"0","checktype":"2","checkstatus":"1","isdeleted":"0","tagself":"","taghost":"","source":"2","location":"","address":"","rank":"-1","custom":"{\"nid\":\"NEW2018021000440000\"}","extend":{"at":0,"ut":0,"ct":"","wt":0},"orireplynum":"0","richtype":0,"userid":"244293709","poke":0,"abstract":"","thirdid":"","ispick":0,"ishide":0,"isauthor":0,"replyuser":"","replyuserid":0,"replyhwvip":0,"replyhwlevel":0,"replyhwannual":0,"userinfo":{"userid":"244293709","uidex":"ec8f33bc266f2542debc1b0fde2ae4f35c","nick":"sunshine","head":"http:\/\/q2.qlogo.cn\/g?b=qq&k=PkjialNicxiaia4aBmEbUvC2Fw&s=40&t=1518278400","gender":1,"viptype":"0","mediaid":0,"region":"\u4e2d\u56fd:\u7518\u8083:\u5929\u6c34","thirdlogin":0,"hwvip":0,"hwlevel":0,"hwannual":0,"identity":"","wbuserinfo":[],"certinfo":"","remark":"","fnd":0}}],
"targetinfo":{"orgcommentnum":"21454","commentnum":"2236","checkstatus":"0","checktype":"2","city":"","voteid":"","topicids":"","commup":"57857"}},"info":{"time":1518356838}}
orgcommentnum = commentnum+所有回复数
commup (点攒数)
orgcommentnum":"13","commentnum":"9","checkstatus":"0","checktype":"1","city":"","voteid":"","topicids":"","commup":"4"
orireplynum 表求有回复,几条,3层的不在计数内
v2版本
每页30条评论
使用F12 开发者工具,发现获取数据url
腾讯网
pageflag=1 表示降序 2表示升序
cursor=6368375395179841307 上一次最后一个id
orinum=10表示原始数据数 最大30
oriorder=o 表示最热排序 ,t表示时间排序
orirepnum=2 每条原始评论的显示回复数
reporder=o
腾讯网&orinum=30&oriorder=t
评论回复
rootComUrl='http://coral.qq.com/comment/%s' \
'/reply/v2?targetid=%s' \
'&reqnum=2&pageflag=2&cursor=%s'
maxCommNum=1
preLastId='0' #上一次请求的最后一个评论ID
totalCount=0
while totalCount
#articleCmntId就是 上边提到的cmnt_id值
url = rootComUrl%(评论ID,articleCmntId,preLastId)
6 注意
QQ的跳转太多,只有在请求滚动页面时需要加头,其他都可以不用
cmnt_id在不同的页面可能不太一样,有的是下边两种格式
comment_id
aid
2 新浪
说明:这个是新闻数据最多的,最活跃的。
1 准备爬取滚动页面
滚动页面地址:http://roll.news.sina.com.cn,显示如下
2 滚动页面类别,只是部分,往后和的url 基本都不更新了
http://roll.news.sina.com.cn/s/channel.php?ch=00 滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=01 新闻中心滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=02 体育滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=03 财经滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=04 娱乐 utf8
http://roll.news.sina.com.cn/s/channel.php?ch=05 科技滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=06 军事滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=07 股市滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=08 美股滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=09 世界杯滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=10 世界杯新闻定制滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=11 iPad视频滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=12 科技数码滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=13 财经ipad滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=14 2010亚运会滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=15 iPad-NBA滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=16 收藏滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=17 iPad视频客户端滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=18 航空滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=19 教育滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=20 女性滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=21 读书滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=22 时尚滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=23 深度滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=24 高尔夫滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=25 汽车滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=26 2012奥运项目滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=27 2012奥运项目滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=28 All News - SINA English
http://roll.news.sina.com.cn/s/channel.php?ch=29 星座滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=30 育儿滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=31 尚品滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=32 佛学滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=33 健康滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=34 大时尚滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=35 图片滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=36 博客滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=37 美食滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=38 旅游滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=39 房产滚动新闻_新浪网
http://roll.news.sina.com.cn/s/channel.php?ch=40 北美全球新闻滚动新闻_新浪网
3 动态获取滚动页面数据
rollUrl = 'http://roll.news.sina.com.cn/interface/rollnews_ch_out_interface.php?col=%s' \
'&spec=&type=1&date=%s' \
'&ch=01&k=&offset_page=0&offset_num=0&num=%s' \
'&asc=&page=%s'
rollCol_1 = '90,91,92' #表示新闻大类中的3个小类,国内,国际,社会
# rollData_2=''
rollNum_3 = 80 #一次请求80条数据,可以改,不一定是40 60 80,可以随意改
rollPage_4 = 1
#date = 表示同QQ一样 如date=2018-02-11
#ch=01 表示意思看上一小节内容
#type=1 表示3种类型,文本,视频,图片
4 获取的动态页面新闻条目
var jsonData = { serverSeconds : 1518609995,
last_time : 1517845981,
path : [{title : "社会", id : "92", cType : "col"}],
count : 120,
offset_page : 0,
offset_num : 0,
list : [ {channel : {title : "社会",id : "92",cType : "col",url : ""},title : "男子要求为妻换病房遭拒后殴打护士 警方:已刑拘",url : "http://news.sina.com.cn/c/2018-02-05/doc-ifyreuzn3452012.shtml",type : '1',pic : '',time : 1517845981}] };
这个我是使用python 调用js的JSON序列化后再转成python字典,可以看目录10中内容。
5 获取评论内容
一次最多获取200条
urlRoot = 'http://comment5.news.sina.com.cn/page/info?version=1&format=json&channel=%s&newsid=%s&&page=%d&page_size=200&ie=utf-8&oe=utf-8&group=undefined&compress=0'
url = urlRoot % (channel, newsid, 1)
以下两个参数,是从内容页面中获取的
channel
newsid
sh:comos-fyreuzn3452012" />sh;comment_id:comos-fyreuzn3452012" />
获得的数据是json格式 直接使用json.loads 加载
otherResponse = requestUrl(url)
if otherResponse is None:
return 点赞数, 评论数, commentList
if not otherResponse.ok or otherResponse.status_code != 200:
logger.info("评论请求失败 %s" % (url))
return 点赞数, 评论数, commentList
jsonpStr = otherResponse.text
jsonp = json.loads(jsonpStr)
评论数据:
{"result": {"status": {"msg": "", "code": 0}, "count": {"qreply": 1578, "total": 1650, "show": 54}, "replydict": {"5A7
点赞数 = jsonp['result']['count']['total'] # total 参与数 qreply 赞同数 total!=qreply+show
评论数 = jsonp['result']['count']['show']
3 网易新闻
注意:网易新闻不可以爬取历史数据就3天数据的,有评论。
1 滚动新闻
只有一页,不能看往期回顾的历史数据,
这一页数据3天左右,请求地址:http://news.163.com/special/0001220O/news_json.js?0.5738355523554022
正则提取url:
urlList += re.findall('http://news.163.com/[\d]{2}/[\d]{4}/[\d]{2}/[\\S]*.html', response.text)
2 页面内容爬取,
我使用'http://news.163.com/shehui/', 'http://news.163.com/special/0001220O/news_json.js?0.5738355523554022' 两个地址作为种子,开始使用scrapy开始爬取数据。
3 获取评论内容
urlRoot = 'http://comment.news.163.com/api/v1/products/%s/threads/%s'
commentList = []
点赞数 = 0
评论数 = 0
url = urlRoot % (productKey, newsid)
参数
productKey
newsid
newsid 可以从url中获取
productKey 从正文页面script中获取如下js数据。
var config = {
"productKey" : "a2869674571f77b5a0867c3d71db5856",
"docId" : "DAG0F9SL0001885B",
"target" : document.getElementById("post_comment"), //Dom 容器
//展示的功能按钮:顶、踩、回复、收藏、举报、分享
"operators": ["up", "down", "reply", "share"],
"isShowComments": isShowComments, //是否显示帖子列表
"hotSize": 3, //热门跟贴列表 展示 3 条
"newSize": 2, //最新跟贴列表 展示 2 条
"submitType": "commentPage" //新发帖子的展现形式:停留在当前页面(currentPage) | 跳转到跟贴详情页(commentPage)
};
使用正则
# a.*c
# 对从a到c的字符串最长匹配
# a.*?c
# 对从a到c的字符串最短匹配
t_configDict = re.search('var config = {(.*?)};', response.text, re.S)
这个数据不能直接加载到json.loads。也可以直接获取"productKey" : "a2869674571f77b5a0867c3d71db5856",,中productKey的值,正则:
productKey = re.search('"productKey" : "(.*?)",', response.text, re.S)。
也可以使用目录10方式,转换为python字典。
4 评论数据格式
# {"against": 0,
# "boardId": "news2_bbs",
# "channelId": "0001",
# "cmtAgainst": 4, 反对计数
# "cmtVote": 3955, h点赞数,跟贴会在这里加
# "createTime": "2018-02-12 08:47:55",
# "docId": "DAEC3F0G00018AOQ",
# "isAudit": false,
# "modifyTime": "2018-02-12 10:05:27",
# "pdocId": "DAEC3F0G00018AOQ",
# "rcount": 272, 还不知道是做什么的
# "status": {"against": "on", "audio": "off", "web": "on", "joincount": "on", "label": "on", "app": "on"},
# "tcount": 242, 评论数, 发起评论数
# "title": "三亚春节期间日均车流量将达55万辆 交警取消休假",
# "url": "http://news.163.com/18/0212/08/DAEC3F0G00018AOQ.html",
# "vote": 64} 表示当前评论的点赞数
4 南方周未
说明:数量不多,评论内容基本没有
1 滚动爬取
滚动首页:找准“孤岛”事件的病根 | 南方周末/
找准“孤岛”事件的病根 | 南方周末/+pageNum
使用 scrapy 的CSS提取url :
# 提取内容链接
contentLinks = response.css('div[class*=articleTitle] a::attr(href)').extract()
2 评论内容太少,没看
5 环球网
说明:数据量还可以,滚动新闻可以有1800条,每一个类别
1 滚动爬取
使用scrapy,以三个滚动内容为种子: 'http://society.huanqiu.com/article/', 'http://china.huanqiu.com/article/',
'http://world.huanqiu.com/article/'
如下图:
翻页,每个类另就30页内容,差不多有不到3个月的数据。
for i in range(2,31):
url = response.url+str(i)+'.html'
print("pares:", url)
提取url :
t = re.findall(response.url+'[\d]{4}-[\d]{2}/[\d]{8,}.html', otherResponse.text)
2 评论太少没看
6
7 中国新闻网
说明:爬取, 社会,和国际,国内, 要闻。这个爬虫,只爬内容,不爬评论。
1 滚动新闻
这个滚动比较好,不用翻页,反的内容在一页里。
rootUrl='http://www.chinanews.com'
rollUrl='http://www.chinanews.com/scroll-news/%s/%s/%s%s/news.shtml'
# 爬虫类别,社会,国际,国内
rollCol_1 = 'sh,gj,gn'
for col in rollCol_1:
nextUrl = rollUrl % (col, YYYY, MM,DD)
2 获取所有的正文 url
def __get_web_xml_dict(self,text):
doc = soup(text, 'html5lib')
res =doc.select('div[class=content_list] a')
resdictList = [x['href'] for x in res]
return resdictList
3 评论太少,没看
8 搜狐
1 sohu没有可有滚动新闻页面
以社会版为基础,搜狐 向下拉动页面时动态加载。
使用F12 查看出加载js数据链接:
完整的社会加载页面是这样的
http://v2.sohu.com/public-api/feed?scene=CHANNEL&sceneId=43&page=3&size=20&callback=jQuery112409024281900407367_1518523290701&_=1518523290747
社会页面,删除不必要的后两个字段,返回的是一个 字典list
http://v2.sohu.com/public-api/feed?scene=CHANNEL&sceneId=43&page=2&size=500
字典list 主要获取两个值
"id":222662942,"authorId":656058,"
url参数说明:
sceneId=43 类型号,43表示社会类。
page=2&size=500 可以随意改动 page 分页,size 表示每页多少条新闻。
拼接正文URL:
内容页面是通过拼接出来的
404,您访问的页面已经不存在!
http://www.sohu.com/a/ + ID + authorId + ?_f=index_chan43news_1
2 评论获取
评论地址:
http://apiv2.sohu.com/api/comment/list?callback=jQuery112408923300187175247_1518527025167&page_size=10&topic_id=1232684&page_no=2&source_id=mp_222586905&_=1518527025198
可用减少的参数:
http://apiv2.sohu.com/api/comment/list?page_size=10&topic_id=1232684&page_no=2&source_id=mp_222586905
参数说明:
source_id = mp_+上一节的id,不是作者id
topic_id获取,这个client_id=cyqemw6s1 不是我的,网上别人的。
http://changyan.sohu.com/api/3/topic/liteload?client_id=cyqemw6s1&topic_url= +URL
例
http://changyan.sohu.com/api/3/topic/liteload?client_id=cyqemw6s1&topic_url=http://www.sohu.com/a/222568275_119562
获取topic_id内容
{"cmt_sum":0,"comments":[],"hots":[],"mode":6,"outer_cmt_sum":0,"participation_sum":0,"topic_id":4458981604}
获取评论内容
http://apiv2.sohu.com/api/comment/list?page_size=10&topic_id=4458981604&page_no=2&source_id=mp_222568275
{"code":200,
"msg":"SUCC",
"jsonObject":{"cmt_sum":70,
"participation_sum":534,
"author_user_id":0,
"source_id":null,
"outer_cmt_sum":70,
"total_page_no":70,
"hots":[],
"topic_id":1232498,
"comments":[{"content":"心理素质是培养出来的,不是先天的。没人对他好好培养,不是他自己的错","comment_id":5314948,"status":1,"reply_count":0,"reply_id":5314432,"support_count":0,"create_time":1518530874815,"user_id":191688,"from":"client","attachments":[],"passport":{"img_url":"https://sucimg.itc.cn/avatarimg/320104377_1450609122085","nickname":"不爱动脑","user_id":191688},"comments":[{"content":"心理素质太差了","comment_id":5314432,"status":1,"reply_count":1,"reply_id":0,"support_count":1,"create_time":1518530042690,"user_id":498816,"from":"client","attachments":[],"passport":{"img_url":"https://sucimg.itc.cn/avatarimg/540734539_1480586746532_c175","nickname":"visitor607623095","user_id":498816},"comments":null,"ip_location":"安徽省","ip":"117.136.101.37"}],"ip_location":"北京市北京市","ip":"180.88.190.165"}]
}
}
点赞数 = jsonp['jsonObject']['participation_sum']
评论数 = jsonp['jsonObject']['cmt_sum']
9 央视网
1 滚动页面
央视网页面,每次获取的数据是固定的,
比如社会新闻_央视网(cctv.com),社会版。
获取所有数据:http://news.cctv.com/society/data/index.json,是一个json数据
http://news.cctv.com/society/data/index.json
以下替换society就可以获取其他版的数据
law
word
society
tech
world
china
2 正则获取正文 url
t = re.findall('http://news.cctv.com/[\d]{4}/[\d]{2}/[\d]{2}/[\\S]*.html', otherResponse.text)
10 python 执行js 脚本
execjs 方法,尝试可有
有好几种,试了下都没成功,这个可以
1 执行函数
import execjs
# source = """
# 你的这段js
# """
#
# print (execjs.compile(source).call('加密方法', '参数1', '参数2'))
source="""
function add(x, y) {
return x + y;
}
"""
print (execjs.compile(source).call('add',1,2))
import json
a = '{"name":"aa","age":45}'
b = json.loads(a)
print(b)
2 js 的JSON数据输出为序列,再转为python JSON
import requests
# json解析库,对应到lxml
import json
# json的解析语法,对应到xpath
import execjs
data = """var config = {
"productKey" : "a2869674571f77b5a0867c3d71db5856",
"docId" : "DAG0F9SL0001885B",
"target" : document.getElementById("post_comment"), //Dom 容器
//展示的功能按钮:顶、踩、回复、收藏、举报、分享
"operators": ["up", "down", "reply", "share"],
"isShowComments": isShowComments, //是否显示帖子列表
"hotSize": 3, //热门跟贴列表 展示 3 条
"newSize": 2, //最新跟贴列表 展示 2 条
"submitType": "commentPage" //新发帖子的展现形式:停留在当前页面(currentPage) | 跳转到跟贴详情页(commentPage)
};"""
data=data.replace('document.getElementById("post_comment"),','"",')
data=data.replace('isShowComments,','"",')
print(data)
'''加载 js 的JSON库'''
f = open("../utils/JSON-js-master/json2.js", 'r', encoding='UTF-8')
js_json_function = f.read()
# print(js_json_function)
js_json_function = js_json_function+"""
function jsonToXuLeiHua(){
%s
return JSON.stringify(%s);
}
"""%(data,'config')
# print(js_json_function)
res = execjs.compile(js_json_function).call('jsonToXuLeiHua')
# print (execjs.compile(js_json_function).call('jsonToXuLeiHua'))
print(type(res),res)
unicodestr = json.loads(res)
# python形式的列表
print(type(unicodestr),unicodestr)
3 JSON-js 包