欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python爬取百度文库付费文档(PDF)

程序员文章站 2022-07-03 21:03:55
...

Python爬取百度文库付费文档(PDF)@TOC
PS:本文为本人学习python的一个小分享,仅供学习和参考使用,不可做商业利益的盗取!

工具准备

Python3.x;
Python库:selenium、requests;

爬取页面及爬取分析

https://wenku.baidu.com/view/9a5a21cf964bcf84b9d57bea?pn=50
打开网址,鼠标置于第一页上,右键打开元素,可以看到图片来自于一个链接。
Python爬取百度文库付费文档(PDF) 我们打开这个链接。
Python爬取百度文库付费文档(PDF)可以很清楚的看到,这是一个图片的下载链接。所以我们需要爬取这个文档,有一个很清晰的思路就是把每一页的图片链接截取下来,然后再通过requests打开该链接来保存图片,最后将所有图片合成PDF即可。

功能模块代码的实现

实现图片链接的获取

首先,先介绍下selenium库,Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。所以,你在人工操作浏览器时可以看到的东西,在selenium也可以看到。这里,我们就使用selenium来获取这个页面的源代码。

from selenium import webdriver
driver = webdriver.Firefox() #selenium支持火狐、谷歌等浏览器
driver.get('https://wenku.baidu.com/view/9a5a21cf964bcf84b9d57bea?pn=50')
print(driver.page_source)

我们先打印下源码看看。由于源码太长,我只贴出跟我们需要爬取内容相关的部分。

<div class="bd" id="pageNo-1" data-page-no="1" data-mate-width="892.83" data-mate-height="1263" style="height:1011.4411478109px;" data-scale="0.70691211401425" data-render="1"><div class="reader-parent-a929d320eefdc8d376ee3224 reader-parent " style="position:relative;top:0;left:0;-moz-transform:scale(0.99);-moz-transform-origin:left top;"><div class="reader-wrapa929d320eefdc8d376ee3224" style="position:absolute;top:0;left:0;width:100%;height:100%;"><div class="reader-main-a929d320eefdc8d376ee3224" style="position:relative;top:0;left:0;width:100%;height:100%;"><div class="reader-pic-layer" style="z-index:1"><div class="ie-fix"><div class="reader-pic-item" style="background-image: url(https://wkbjcloudbos.bdimg.com/v1/docconvert4643//wk/caae51448eb3233830370d1963fa165e/0.png?responseCacheControl=max-age%3D3888000&amp;responseExpires=Sun%2C%2019%20Jan%202020%2014%3A13%3A06%20%2B0800&amp;authorization=bce-auth-v1%2Ffa1126e91489401fa7cc85045ce7179e%2F2019-12-05T06%3A13%3A06Z%2F3600%2Fhost%2Fc895ec975bc2d19dc54d3f80ffe35c865e469fdabcd3501ae7ad5af14af5896a&amp;x-bce-range=0-84983&amp;token=eyJ0eXAiOiJKSVQiLCJ2ZXIiOiIxLjAiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU3NTUyOTk4NiwidXJpIjp0cnVlLCJwYXJhbXMiOlsicmVzcG9uc2VDYWNoZUNvbnRyb2wiLCJyZXNwb25zZUV4cGlyZXMiLCJ4LWJjZS1yYW5nZSJdfQ%3D%3D.15JLNbl07gJ1bGi0wBW6Jqm%2BPpuUyz7gJlsdIgf%2FdLc%3D.1575529986);background-position:0px 0px;width:812px;height:1179px;z-index:1360;left:73.7565px;top:86.32625000000002px;opacity:1;-moz-transform: scale(1.0685,1.0685);position:absolute;overflow:hidden;"></div></div></div></div></div></div></div>

我们可以看到,该图片元素ID为“pageNo-1”,图片链接在style="background-image:中。通常我们爬取源码都是利用BeautifulSoup库与lxml配合解析的。但这里利用这两个库解析都打印的源码存在丢失现象,可能的原因是该源码编码中存在gb2312编码。我也尝试了不同的解析库,都不行。于是,这里我们可以另辟蹊径,将所有的网页源码存于一个字符串中,然后对字符串进行一系列的处理,得到我们需要的url连接。

mystring = driver.page_source; #将源码赋值给一个字符串
page = 'pageNo-1'
idx1 = mystring.find('<div class="bd" id="' + page) #找出<div class="bd" id="pageNo-1的索引
mystring = mystring[idx1:] #通过索引先将索引前面的所有字符串删除
idx1 = mystring.find("https") #我们可以发现URL的链接都是已https开头的
idx2 = mystring.find(");background-position") #URL的结尾后面都跟着“);background-position“
mystring = mystring[idx1:idx2] #然后再根据这两个索引,截取URL连接。这里需要注意的是python前后缩影截取为左闭右开的区间,及[idx1:idx2)
pringt(mystring)#输出处理后的字符串检查

打印结果为:https://wkbjcloudbos.bdimg.com/v1/docconvert4643//wk/caae51448eb3233830370d1963fa165e/0.png?responseCacheControl=max-age%3D3888000&responseExpires=Sun%2C%2019%20Jan%202020%2014%3A13%3A06%20%2B0800&authorization=bce-auth-v1%2Ffa1126e91489401fa7cc85045ce7179e%2F2019-12-05T06%3A13%3A06Z%2F3600%2Fhost%2Fc895ec975bc2d19dc54d3f80ffe35c865e469fdabcd3501ae7ad5af14af5896a&x-bce-range=0-84983&token=eyJ0eXAiOiJKSVQiLCJ2ZXIiOiIxLjAiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU3NTUyOTk4NiwidXJpIjp0cnVlLCJwYXJhbXMiOlsicmVzcG9uc2VDYWNoZUNvbnRyb2wiLCJyZXNwb25zZUV4cGlyZXMiLCJ4LWJjZS1yYW5nZSJdfQ%3D%3D.15JLNbl07gJ1bGi0wBW6Jqm%2BPpuUyz7gJlsdIgf%2FdLc%3D.1575529986
我们打开这个链接,发现无法打开。回到我们手动打开的页面,复制一下正确的网址与之进行对比。
正确的URL:https://wkbjcloudbos.bdimg.com/v1/docconvert4643//wk/caae51448eb3233830370d1963fa165e/0.png?responseCacheControl=max-age%3D3888000&responseExpires=Sun%2C%2019%20Jan%202020%2011%3A27%3A12%20%2B0800&authorization=bce-auth-v1%2Ffa1126e91489401fa7cc85045ce7179e%2F2019-12-05T03%3A27%3A12Z%2F3600%2Fhost%2Faf0214c3c956c28ad864419c09c58541d9f6d00fd7a73c92624fc7aa69b484ed&x-bce-range=123043-197963&token=eyJ0eXAiOiJKSVQiLCJ2ZXIiOiIxLjAiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU3NTUyMDAzMiwidXJpIjp0cnVlLCJwYXJhbXMiOlsicmVzcG9uc2VDYWNoZUNvbnRyb2wiLCJyZXNwb25zZUV4cGlyZXMiLCJ4LWJjZS1yYW5nZSJdfQ%3D%3D.uGj%2F8Z9kAT4ldgOX62nSX2By9c7pOhNhA1g90oRH%2Bxg%3D.1575520032
这里请忽略链接的不同,该图片链接本身就是动态链接,每次链接都不同,我们只需要关注格式。对比发现,我们打印的URL中多出了几个“amp;”
https://wkbjcloudbos.bdimg.com/v1/docconvert4643//wk/caae51448eb3233830370d1963fa165e/0.png?responseCacheControl=max-age%3D3888000&amp;responseExpires=Sun%2C%2019%20Jan%202020%2014%3A13%3A06%20%2B0800&==amp;==authorization=bce-auth-v1%2Ffa1126e91489401fa7cc85045ce7179e%2F2019-12-05T06%3A13%3A06Z%2F3600%2Fhost%2Fc895ec975bc2d19dc54d3f80ffe35c865e469fdabcd3501ae7ad5af14af5896a&amp;x-bce-range=0-84983&amp;token=eyJ0eXAiOiJKSVQiLCJ2ZXIiOiIxLjAiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU3NTUyOTk4NiwidXJpIjp0cnVlLCJwYXJhbXMiOlsicmVzcG9uc2VDYWNoZUNvbnRyb2wiLCJyZXNwb25zZUV4cGlyZXMiLCJ4LWJjZS1yYW5nZSJdfQ%3D%3D.15JLNbl07gJ1bGi0wBW6Jqm%2BPpuUyz7gJlsdIgf%2FdLc%3D.1575529986
我们把“amp;”删除了试试。mystring = mystring.replace('amp;', '')
再次打印后打开链接就可以发现,可以正常下载了。到此,图片链接获取功能实现。

实现图片的下载

图片的下载我们可以使用requests.content来实现。

r = requests.get(mystring)
with open(page + '.png', 'wb') as png:
	png.write(r.content)
png.close()

运行后就可以看到一个pageNo-1.png图片的生成。打开看看,果然就是我们刚刚看到的第一页。

实现浏览器下滑及按钮的点击

上面两个模块只是实现的第一页的图片的下载。百度文库中,第三页都免会有一个打开更多页的按钮,我们需要点击该按钮才可以获得更多的页面。我们先通过浏览器获得该按钮元素的ID或者class name。

<div id="html-reader-go-more" class="banner-wrap more-btn-banner" style="height: auto;">
<div class="banner-core-wrap super-vip"><div class="doc-banner-text">下载文档到电脑,使用更方便</div>    <div class="doc-banner-value"><div><span class="icon-ticket"></span>5下载券</div><div class="doc-banner-ticket-rights">(您持有<span>0</span>下载券,积分不足无法兑换)</div></div><div class="doc-banner-btns super-vip">                <div class="btn-pay-vip"><i></i>VIP免费下载</div></div><div class="doc-banner-tip super-vip"><div class="doc-banner-rights-wrap"><span class="icon-triangle" style="left: 221.5px;"></span><div class="download-pro-doc"><i></i>享VIP专享文档下载特权</div><div class="download-share-doc"><i></i>赠共享文档下载特权</div><div class="yuedu-vip"><i></i>赠百度阅读VIP精品版</div></div></div></div>
<div class="continue-to-read">
<div class="banner-more-btn">
<span class="moreBtn goBtn">
<span>还剩63页未读,</span>
<span class="fc2e">继续阅读</span>
</span>
<p class="down-arrow goBtn"></p>
</div>
<div class="hengxian"></div>
</div>
<div class="wubai-wrap">
<div class="wubai-title">
<span class="wubai-icon"></span>

很直观的就可以看到,该按钮class=“down-arrow goBtn”。通过xpath来定位该按钮,再进行click的动作即可实现点击按钮。

button = driver.find_element_by_xpath("//p[@class='down-arrow goBtn']")
button.click()
time.sleep(2) #请在头文件加入 import time,单位秒

这里我引入了time库的sleep函数,是考虑到网速问题,让浏览器缓冲一下。
按钮的点击实现了。我们还会发现一个问题,那就是有时候后面几页的图片无法加载,这里我的处理方式是,没爬取一个页面就讲滑条滑动到该页,同时缓冲2S保证图片的加载成功。很可惜selenium没有关于浏览器滑条的操作,这里需要配合JS进行滑条的操作。

target = driver.find_element_by_id(page)#page={pageNo-1,pageNo-2,....}是图片的ID,上文有提到
driver.execute_script("arguments[0].scrollIntoView();", target)#上一句是找到该元素,这一句是运用JS滑动滑条到该元素
time.sleep(2)

代码的集合

上面那个章节将我们需要的功能都实现了(其实还有一个功能模块我没有弄,那就是图片合成PDF。但是我比较懒,而且现在WORD直接一键插入所有图片,然后输出成PDF比较方便,所以这里我就没弄,只是保存了所有的图片),最后我们只需要进行整合即可。
此处该文档共有两个链接。
1-50页链接为:https://wenku.baidu.com/view/9a5a21cf964bcf84b9d57bea?pn=50
50-66页链接为:https://wenku.baidu.com/view/9a5a21cf964bcf84b9d57bea?pn=51
所以爬取的时候记得更换链接。
代码集合如下:

from selenium import webdriver
import time
import requests

driver = webdriver.Firefox()
driver.get('https://wenku.baidu.com/view/9a5a21cf964bcf84b9d57bea?pn=50')
for i in range(50):
    if i == 3:
        target = driver.find_element_by_id('html-reader-go-more')
        driver.execute_script("arguments[0].scrollIntoView();", target)
        button = driver.find_element_by_xpath("//p[@class='down-arrow goBtn']")
        button.click()
        time.sleep(2)
    page = 'pageNo-' + str(i+1)
    print(page)
    target = driver.find_element_by_id(page)
    driver.execute_script("arguments[0].scrollIntoView();", target)
    time.sleep(2)
    mystring = driver.page_source;
    idx1 = mystring.find('<div class="bd" id="' + page)
    print(idx1)
    mystring = mystring[idx1:]
    idx1 = mystring.find("https")
    print(idx1)
    idx2 = mystring.find(");background-position")
    print(idx2)
    mystring = mystring[idx1:idx2]
    mystring = mystring.replace('amp;', '')
    if mystring.startswith('http') :
        r = requests.get(mystring)
        with open(page + '.png', 'wb') as png:
            png.write(r.content)
        png.close()
    else:
        print(mystring)

driver.get('https://wenku.baidu.com/view/9a5a21cf964bcf84b9d57bea?pn=51')
for i in range(50,66):
    page = 'pageNo-' + str(i + 1)
    print(page)
    target = driver.find_element_by_id(page)
    driver.execute_script("arguments[0].scrollIntoView();", target)
    time.sleep(2)
    mystring = driver.page_source;
    idx1 = mystring.find('<div class="bd" id="' + page)
    print(idx1)
    mystring = mystring[idx1:]
    idx1 = mystring.find("https")
    print(idx1)
    idx2 = mystring.find(");background-position")
    print(idx2)
    mystring = mystring[idx1:idx2]
    mystring = mystring.replace('amp;', '')
    if mystring.startswith('http'):
        r = requests.get(mystring)
        with open(page + '.png', 'wb') as png:
            png.write(r.content)
        png.close()
    else:
        print(mystring)
driver.quit()

总结

1.selenium确实好用,运用好了可以做很多事情;
2.BeautifulSoup与lxml无法解析网页时,多换种思路,也可以实现功能;
3.新学python,若有错误,请大神们轻喷,也请指正,谢谢;
4.本文仅供学习python的selenium库的学习参考,切勿用于商业利益,我们要尊重别人的知识产权。