免费自动下载豆丁网的资料
Download materials in Docin freely and automatically
本工具已封装为EXE可执行程序,发布了release,可直接下载使用,无需搭建任何环境(想要了解实现细节的可以继续往下看)
如遇图片显示不全,请到我的博客观看,欢迎点赞留言!
最近在查找资料时,在豆丁网上找到不少有用的资料,比如说一些课程的PPT之类的,但是只能在线看,而且还有广告,但是下载下来要钱,而且要价不菲,于是动起了歪脑筋,寻思着搞一个爬虫直接下载下来就可以离线看了,也方便资料的存储管理。本教程已完结,请放心食用,效果如下(该效果图采用screentogif软件录制,特此鸣谢):
本代码免费开源,不想了解原理的可以跳过到使用部分直接使用,希望能给我点个赞以支持开发,如果方便的话,github来一颗星星更好啦!
开源代码地址:https://github.com/you8023/downloadDocin,直接下载按照使用方法使用即可,如遇问题,欢迎在文章下方留言或在github上提issue。
- Windows 10
- Sublime Text 3
- Python 3.8
- python库:lxml、fpdf、requests
- 首先安装Python,直接到官网下载安装即可
- 安装Python库
键盘同时按下
win
+R
,在弹出的对话框中输入cmd
按回车 在弹出的黑框中输入命令安装python库:
pip install lxml
pip install fpdf
pip install requests
至此,环境搭建完毕
在编写代码前,需要对需求及网页进行分析,明确我们需要的东西的位置
首先明确需求,我们需要将豆丁网上我们需要的资料爬取下来,通过对页面元素进行观察,发现上面的资料,无论何种格式,均是以图片的形式进行展示,因此,考虑将其保存为pdf以方便查看。提取的输入输出如下:
- 输入:所需资料的网址
- 输出:资料的pdf文件,其中包含:
- 资料的每张图片
- 资料标题
打开想要的资料的网页,这里以这个网页为例,首先,鼠标右键检查,找到标题元素:
可以发现,标题所属的为span
标签,class为doc_title fs_c_76
,因此,可以据此定位标题所在
再以此方法找到资料所属的标签:
发现网页的资料均是以图片形式显示,因此,目标确定,我们仅需要找到这个链接即可
复制链接,直接用浏览器打开,发现果然就是我们要找的这张图片
接着,分析链接https://docimg1.docin.com/docinpic.jsp?file=456842624&width=942&sid=LE-fLs-BXk4g4mtVLS2D8apgA9Z5X3NNeoZbh0mHZkW*C1Zz1LvKe8xey1BsO1BG&pageno=1&pcimg=1
,发现其中有三个关键字段,分别是:
- file
- sid
- pageno
其中,经过观察,发现file
字段的数字和网页链接https://www.docin.com/p-456842624.html
的数字一致;
pageno
字段则是第几页;
而sid
经过打开另一个资料的网页测试,发现不同的资料具有不同的sid
,观察其编码,没发现规律,最终,经过仔细分析,在source
页面源码中找到了一个关键字段flash_param_hzq
:
虽然和sid
字段并不完全一致,但使用该字段作为sid
也能得到图片,因此,提取该网页的flash_param_hzq
字段即可;
页面分析完毕,即可开始编写代码。
首先,使用requests
获取网页内容,并使用lxml
将其封装为一个HTML对象方便后续解析:
html = etree.HTML(requests.get(originUrl).text)
然后使用xpath
提取标题:
title = html.xpath('//span[@class="doc_title fs_c_76"]/text()')[0]
其中,//span
表示匹配任意span
标签,使用[@class=""]
匹配class
属性,使用/text()
提取标签内的内容,由于返回的内容为一个元祖,因此使用[0]
取第一个元素
使用正则表达式匹配file
字段:
fileId = re.findall('\-\d+\.',originUrl)[0][1:-1]
其中,\d
代表匹配数字,+
表示匹配一次或多次,[1:-1]
表示取结果的第二个字符到倒数第二个字符
将HTML对象转为字符串:
theHTML = etree.tostring(html).decode('utf-8')
使用正则表达式匹配flash_param_hzq
字段:
sid = re.findall('flash_param_hzq:\"[\w\*\-]+\"', theHTML)[0][17:-1]
其中,\w
表示匹配数字或字母
至此,该部分函数书写完毕,完整代码为:
def getTiltleUrl(originUrl):
html = etree.HTML(requests.get(originUrl).text)
theHTML = etree.tostring(html).decode('utf-8')
# print(theHTML)
try:
title = html.xpath('//span[@class="doc_title fs_c_76"]/text()')[0]
except:
title = html.xpath('//title/text()')
fileId = re.findall('\-\d+\.',originUrl)[0][1:-1]
sid = re.findall('flash_param_hzq:\"[\w\*\-]+\"', theHTML)[0][17:-1]
url = 'https://docimg1.docin.com/docinpic.jsp?file=' + fileId + '&width=1000&sid=' + sid + '&pcimg=1&pageno='
return title, url
通过上面的函数获取到通用的链接后,仅需要更改pageno
字段即可获取所有图片,使用requests
获取到图片后,直接将文件流写入到文件中即可。但在后续代码运行过程中发现图片格式报错,因此,使用PIL
标准化图片。完整代码如下:
def getPictures(theurl, path):
pagenum = 1
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
}
allNum = 0
while pagenum>0:
# time.sleep(3*random.random())
print('Downloading picture ' + str(pagenum))
url = theurl + str(pagenum)
img_req = requests.get(url=url, headers=headers)
if img_req.content==b'sid error or Invalid!':
allNum = pagenum-1
print('Downloading finished, the count of all pictures is ' + str(allNum))
pagenum = -1
break;
file_name = path + str(pagenum) + '.png'
f = open(file_name, 'wb')
f.write(img_req.content)
f.close()
# 将图片保存为标准png格式
im = Image.open(file_name)
im.save(file_name)
pagenum += 1
return allNum
这里主要使用fpdf
库合并图片为pdf,代码如下:
def combinePictures2Pdf(path, pdfName, allNum):
print('Start combining the pictures...')
pagenum = 1
file_name = path + str(pagenum) + '.png'
cover = Image.open(file_name)
width, height = cover.size
pdf = FPDF(unit = "pt", format = [width, height])
while allNum>=pagenum:
try:
print('combining picture ' + str(pagenum))
file_name = path + str(pagenum) + '.png'
pdf.add_page()
pdf.image(file_name, 0, 0)
pagenum += 1
except Exception as e:
print(e)
break;
pdf.output(pdfName, "F")
其中:
pdf = FPDF(unit = "pt", format = [width, height])
规定了pdf的尺寸pdf.add_page()
将为pdf添加一张空白页面pdf.image(file_name, 0, 0)
则是将图片绘制到该空白页面上,后两个参数为绘制的起始xy坐标pdf.output(pdfName, "F")
则是生成pdf文件
pdf生成完毕之后,之前保存到本地的图片就没有用武之地了,这时需要删去所有图片,删去某个文件的语句为:
os.remove(file_name)
完整代码如下:
def removePictures(path, allNum):
pagenum = 1
while allNum>=pagenum:
try:
print('deleting picture ' + str(pagenum))
file_name = path + str(pagenum) + '.png'
os.remove(file_name)
pagenum += 1
except Exception as e:
print(e)
break;
最后,书写语句依次调用函数,自动化下载图片,合并为pdf,并删去原文件:
if __name__ == '__main__':
path = 'E:\\test\\Docin\\'
# originUrl = 'https://www.docin.com/p-977106193.html?docfrom=rrela'
originUrl = input('input the url: ')
result = getTiltleUrl(originUrl)
title = result[0].split('.')[0]
url = result[1]
print(title, url)
allNum = getPictures(url, path)
pdfName = path + title + '.pdf'
combinePictures2Pdf(path, pdfName, allNum)
removePictures(path, allNum)
其中:
path
为保存文件的路径,注意,必须为转义\
后的绝对路径- 原网址使用
input
函数以让用户自行输入
源码在GitHub上,直接下载即可使用。
- 进入到源码所在的文件夹,使用编辑器(记事本亦可)打开源码文件,修改最下方
main
函数中的path
路径为你想要保存文件的路径 - 在文件所在的文件夹的地址栏输入
cmd
,按下回车,在出现的黑框中输入以下命令:
python downloadPPT.py
- 按下回车,当出现提示语句时输入网址,按下回车
- 静等程序跑完即可,下载的资料在第一步输入的路径里面