当当云阅读云书房电子书内容提取爬虫

当当云阅读云书房电子书内容提取爬虫

一、引言

​ 根据相关法律法规,为了保护作者及版权方的正当权益,当当网电子书目前在PC端不支持下载功能,只能在线阅读。若您订购的电子书支持iPhone、iPad、Android、E-ink设备阅读,请您使用当当读书客户端登陆您的当当账户,之后点击页面左上角图书下载按钮,之后页面中会显示您已购买未下载的图书,请您点击下载即可。

​ 当当云阅读是当当网旗下的电子书阅读平台,具有丰富的正版图书资源。为了保护读物作者和版权方的正当权益,应最大限度地防止相关电子书在互联网上非法传播。当当云阅读在PC端不支持下载,只能在线阅读;在智能手机、平板上需安装当当云阅读客户端进行在线阅读或缓存。

​ 但对大多数用户来说,仅仅能够阅读是不够的,在阅读过程中做批注、摘录等是常有的阅读习惯,但当当云阅读禁止用户复制、粘贴电子书内容,更不能导出到其他阅读器上。如果用户想要快速提取其内容,唯一的解决办法是:截取屏幕存为图片,然后使用OCR工具识别文字,手动校正误识点。——提取量少时这很有效,但如果想要获取章节乃至整本书籍,这就累死傻小子了。

​ 本文对当当云阅读PC端对电子书的保护方式展开研究,提出了一种比传统OCR更高效的电子书内容提取方案:无需截屏、OCR识别、手动校正,并保证提取内容的准确度为100%。基本思路是:

利用网络爬虫模拟登录云阅读网站以获取Cookies信息;

”破解“云阅读页面文字的反爬取策略;

解析数据并批量保存文字内容。

本文提供的内容仅用于学习研究,请勿用于非法用途,否则后果自负。

二、分析过程

2.1 页面api解析

打开一本电子书,其链接如下:

http://e.dangdang.com/pc/reader/index.html?id=1901168632

打开浏览器的网络调试工具或其他抓包程序,点击翻页获取到页面载入的API:

GET http://e.dangdang.com/media/api.go

Query String Parameters:

actiongetPcChapterInfoepubID1901168632电子书idpermanentId20210416104157326126993695645533813商品idconsumeType1platform3deviceTypeAndroiddeviceVersion5.0.0channelId70000platformSourceDDDS-PfromPaltformds_androiddeviceSerialNohtml5clientVersionNo5.8.4tokenchapterID2章节pageIndex1章节页码locationIndex4全局页码wordSize2style2autoBuy0chapterIndex

其中关键参数有:chapterID、pageIndex、locationIndex,在批量爬取时按照章节、页码顺序遍历。

API返回内容:

{

"data": {

"chapter": {},

"chapterInfo": {

"status": "ok",

"code": 0,

"pageType": 0,

"snippet": "......"

},

"systemDate": "1618485378339",

"currentDate": "2021-04-15 19:16:18",

"wordSize": 2

},

"systemDate": 1618485378339,

"status": {

"code": 0

}

}

snippet字段内容是HTML标签,这些标签经过浏览器渲染构成了页面文字,内容截取如下:

t

o

...

2.2 文字排序

通过浏览器元素定位发现,每一个标签对应页面上的一个文字,并通过样式style="left:155px; bottom:392px; "进行定位,这意味着标签并非按照在HTML文档中的顺序排列,而是在浏览器中渲染后得到正确的显示顺序。效果如下图:

利用爬虫虽然能够直接解析API响应取到所有文字,但这是乱序的,必须利用定位信息left和bottom重新排列文字,否则所得结果毫无意义。

文字排序算法伪代码如下:

Array col_list // 列定位数组,存储bottom.px信息

for span in : // 遍历span标签

if span.bottom.px not in col_list:

col_list.add(bottom.px) // 添加列信息

end if

end for

Array order_col_text // 全部行文字

for col.px in col_list: // 遍历列

Array row_text // 行文本数组,存储同一行的文字

Array row_list // 行定位数组,存储left.px信息

for span in : // 遍历span标签

if span.bottom.px == col.px: // 获取同一行的span标签

row_text.add(span.text) // 添加文字到当前行

row_list.add(span.bottom.px) // 添加文字定位

end if

end for

// 此处得到同一行的所有文字,但在同一行内仍是乱序

// 对row_list进行排序,得到正确顺序后再对row_text进行排序

Array order // 对行定位排序后的索引列表

for i in sorted(row_list): // 遍历排序后的定位

order.add(row_list.indexOf(i)) // 添加排序后定位所在索引

end for

// 按照正确顺序进行文字组合

Array order_row_text // 排序后的文字数组

for i in row_list.length:

order_row_text[i] = row_text[order[i]] // 依序组合文字

end for

order_col_text.add(order_row_text)

示例:

row_text: [‘3’, ‘第’, ‘1’, ‘章’]

row_list : [85, 45, 71, 99]

order : [1, 2, 0, 3]

order_row_text:第13章

解释:

[‘3’, ‘第’, ‘1’, ‘章’] 是根据标签顺序获取的文字内容,[85, 45, 71, 99]是对应文字的left定位属性,'3’距离左侧85px,'第’距离左侧45px,'1’距离左侧71像素,'章’距离左侧99px,得到正确顺序应为:[1, 2, 0, 3],即第0个文字取原文字数组的第1个,第1个文字取原数组的第2个,第2个文字取原第0个,第3个文字取原第3个,组合得到:”第十三章“。

三、程序

import requests

import json

from lxml import etree

# 自行替换Cookie信息

headers = {

'Cookie': '',

'Connection': 'keep-alive',

'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'

}

def get_page(chapterID=18, pageIndex=0, locationIndex=315):

url = "" # API,章节、页码id

data = requests.get(url, headers=headers).json()

content = data['data']['chapterInfo']

eles = json.loads(content)

html = eles['snippet'] # 解析得到内嵌html

tree = etree.HTML(html)

spans = tree.xpath('/html/body/div/div/div/span/@style')

# 按照算法伪代码的实现:

col_nums = [] # 列

for span in spans:

col_num = int( span.split(';', 2)[1].strip().split('bottom:')[1].split('px')[0] )

if col_num not in col_nums:

col_nums.append(col_num)

# len(col_nums)

page_text = '\n'

for bottom in sorted(col_nums, reverse=True):

row_texts = tree.xpath('/html/body/div/div/div/span[contains(@style,"bottom:' + str(bottom) + 'px")]/text()')

# print(row_texts)

row_nums = []

for location in tree.xpath('/html/body/div/div/div/span[contains(@style,"bottom:' + str(bottom) + 'px")]/@style'):

row_num = int(location.split(';', 1)[0].split('left:')[1].split('px')[0])

row_nums.append(row_num)

# print(row_nums)

paragraph = False # 是否为段落

# new_row_nums = []

if min(row_nums) != 45:

paragraph = True

else:

paragraph = False

row_results = ['']*len(row_texts)

order = []

for i in sorted(row_nums):

order.append(row_nums.index(i))

# print(order)

for i in range(len(order)):

row_results[i] = row_texts[order[i]]

if paragraph is True:

row_results.insert(0, '\n ')

# print(''.join(row_results))

# return ''.join(row_results)

page_text = page_text + ''.join(row_results)

# 保存结果

print(page_text)

with open(r'D:\Downloads\当当\results.txt', 'a') as f:

f.write(page_text)

# page_text

for i in range(0, 35):

# get_page(18, 0, 315)

get_page(16, i, 252+i)

四、结语

​ 当当云阅读的此种反爬虫措施对大多数用户能起到约束作用,但对普通用户而言,失去了做笔记、复制文字内容等阅读的基本习惯,私以为是得不偿失的。尽管在很大程度上限制了电子书籍的非法传播,却失去了良好的用户体验,特别是对付费用户来说,他们购买书籍后理应享有对其内容的所有权。在互联网商业化、国内版权意识不断得到重视的现如今,这种割舍用户体验以换取权益损失最小化的做法,是互联网公司不得不做出的选择。希望在科技更迭的浪潮中,技术越来越服务于人类,法律法规愈加完善,生活越来越美好!

相关推荐

交易收入多久可以提现到账?
bet28365体育

交易收入多久可以提现到账?

📅 07-10 👁️ 361
学会4种备份MySQL数据库(基本备份方面没问题了)
《杀手2》 图文全剧情流程攻略 全关卡潜入刺杀方案攻略
365网站取款不给怎么办

《杀手2》 图文全剧情流程攻略 全关卡潜入刺杀方案攻略

📅 06-30 👁️ 4784
12月7日是什么日子
365网站取款不给怎么办

12月7日是什么日子

📅 10-12 👁️ 5196
如何利用平安好医生app的步步夺金走路赚钱?
bet28365体育

如何利用平安好医生app的步步夺金走路赚钱?

📅 09-26 👁️ 7121
口袋妖怪日月全交换精灵位置及属性技能一览
GBT36507-2018

口袋妖怪日月全交换精灵位置及属性技能一览

📅 09-01 👁️ 2542