06 Python3.6爬取全言乒乓连载文章并保存pdf和txt电子书

序言

这是一篇集世界乒乓球运动技术演进、发展和不同历史阶段著名运动员简介的百度贴吧帖子。作者全言是我很关注的一个吧友,我本科时候追过的一个他在百度贴吧乒乓球吧连载的一个长帖子,彼时我还是个乒乓小白,看帖子觉得他讲技术演进讲得特别透彻,但是他有点问题,老是对自己写的东西不满意,总是删帖建帖删帖建帖复制粘贴复制粘贴,我的贴吧收藏夹都跟丢了好几次,后来又去乒乓网、百家号搞连载,后来也都逐渐停更,后来他建了一个公众号,把这个连载帖放在里面,但是每篇文章都太短了,需要点进去退出来再点进去,所以我用Python写了一个爬虫脚本,把他这一系列的几百多篇文章给导入成txt电子书文档了。

资源

代码相关准备工作

  • 任务
1. 爬取以下两个网页:网页1网页2里面的所有文章链接,放入一个列表,再传递给下一步;
  • 思路:

这一阶段主要利用selenium来模拟Chrome浏览器获取所有的文章链接。首先要模拟点击不同的页内标签(如红色标注所示),但是由于每个标签下只默认显示十条,只有向下滚动触发js才能加载页内剩余的条目,这个过程属于异步加载。

模拟点击不同的页内标签(如红色标注所示)

  • 分析实现
    这种规模的问题,一般会使用Beautifulsoup库+XHR调试或者selenium.webdriver,但是Beautifulsoup库+XHR调试有问题,在页面下滚捕捉query的时候,看起来像是有什么微妙的规律,但是真正更改query参数的时候,打开的网页还是一模一样,我不得其解,多究无益,果断止损放弃。
    更改query参数后,打开的网页还是一模一样,我不得其解,多究无益,果断放弃

    于是敲定使用selenium
  1. 列表中读取文章链接,打开链接,抓取段落存入txt文件对象,网页利用weasyprint库直接转pdf;
  • 思路:这一步给定了文章链接,由于Beautifulsoup的速度比selenium要快(selenium要打开浏览器),我采用Beautifulsoup
  1. pdf合并。
    使用Pypdf2中的PdfFileMerger方法(from PyPDF2 import PdfFileMerger)合并pdf,但是这种方法不带书签。
    如果执意添加书签超链接,需要from PyPDF2 import PdfFileReader, PdfFileWriter然后一遍addPage一边调用addBookmark,具体使用方法参考
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import os
import requests
from bs4 import BeautifulSoup
from weasyprint import HTML
import ssl
from PyPDF2 import PdfFileReader, PdfFileWriter, PdfFileMerger


outpath = './Table Tennis 24Years'  #输出到根目录指定文件夹,如果没有就创目录
if not os.path.exists(outpath):
    os.makedirs(outpath)

outpathpdf = './Table Tennis 24Years/PDF_folder'
if not os.path.exists(outpathpdf):
    os.makedirs(outpathpdf)


#打开浏览器
# 运行前先下载 chrome driver,下载地址是:https://sites.google.com/a/chromium.org/chromedriver/downloads,点击【Latest Release: ChromeDriver x.xx】进入下载
driver = webdriver.Chrome(executable_path='/Users/miraco/PycharmProjects/grabnet/chromedriver')  # Windows 需写成'./chromedriver.exe'
driver.start_client()  #网页需要模拟浏览器点击

url_pages = ('https://mp.weixin.qq.com/mp/homepage?__biz=MzI5MjY0MTY1Ng==&hid=2&sn=858963d6283870bc173bbb7076a4e620&scene=25#wechat_redirect',
            'https://mp.weixin.qq.com/mp/homepage?__biz=MzI5MjY0MTY1Ng==&hid=6&sn=53bfd170c878ae8b06c868cf8c5c4e34&scene=25#wechat_redirect'
            )  #这是这两个目标网页的网址,我们要把网址里面的所有文章爬出来
tops_css = '#namespace_1 > div.tab_hd > div > div'   #上方目录表标签样式
titles_css = '#namespace_1 > div.tab_bd > div > a > div.cont > h2'   #标签下的题目的样式
hrefs_css = '#namespace_1 > div.tab_bd > div > a'   #每个标签下的超链接样式
info_css  = '#namespace_1 > div.tab_bd > div > a > div.cont > p'     #
all_list = [] #这里面放所有文章的题目、链接、简介

def pgdown():  #页面往下翻滚直到尽头,多次翻滚保证完全加载
    html_page = driver.find_element_by_tag_name('html') #拿到网页对象
    for i in range(8):
        time.sleep(0.5)
        html_page.send_keys(Keys.END)     #模拟对着网页按下键盘'END'的动作


def find_art(url):  #要爬取给定url中的文章的题目、简介、超链接
    lists = []  #这个列表里放要此url可达的文章的题目、梗概、链接
    driver.get(url)  #打开其中一个网页
    time.sleep(3)   #等待网页加载
    buttons = driver.find_elements_by_css_selector(tops_css)  #找到上方目录表标签
    for button in buttons:   #按个激活标签
        time.sleep(2)    #等待网页加载
        button.click()   #点击标签
        pgdown()         #往下滚页
        titles = driver.find_elements_by_css_selector(titles_css)  #找到所有每个标签下的题目对象
        hrefs = driver.find_elements_by_css_selector(hrefs_css)    #找到每个标签下的超链接对象
        intros = driver.find_elements_by_css_selector(info_css)    #找到每个题目下的简介对象
        for title, href, intro in zip(titles,hrefs,intros):
            txt = title.text          #题目对象转文本
            if '):' in txt:          #因为正经文章题目有括号冒号字样,可以依此只找正经编号文章,不找其他
                ref = href.get_attribute('href')    #超链接对象中提取超链接
                lists.append([txt,ref,intro.text])  #符合要求的题目、超链接、简介作为一个子列表,放入大列表中
    return lists

for url in url_pages:  #这是这两个目标网页的网址,都爬出来
    all_list = all_list + find_art(url)
    #得到的是[[a,b,c],[d,e,f],[,g,h,i],[j,k,l]]
    #这里不能用append方法,因为用append以后得到的是[[[a,b,c],[d,e,f]],[[,g,h,i],[j,k,l]]]

driver.quit()  #关浏览器

print(all_list)   #这里打印放所有文章的题目、链接、简介

#爬取到txt

#建立或对已有的此名txt进行内容清空
f = open(os.path.join(outpath,'Table Tennis 24 Years.txt'),'w')
f.close()

#开写开爬,这里爬去使用selenium打开关闭浏览器太慢了,直接上Beautifulsoup,嗖嗖的
f = open(os.path.join(outpath,'Table Tennis 24 Years.txt'),'a') #打开文件对象
f.write('本文档内所有文章皆由"全言乒乓"撰写,Sober作为乒乓球迷苦于其内容支离分散,使用基于Python3.6网络爬虫工具进行文字整理,版权属于"全言乒乓",如侵权请联系我删除!\n\n\n')

def web2txt(f,url,intro):  #给定txt对象、文章链接、简介,将其写入文件
    web_page = requests.get(url)
    soup = BeautifulSoup(web_page.text,'lxml')
    title = soup.select('h2.rich_media_title')[0] #抓取文章页内的题目
    f.write(title.text.strip() + ':' + intro.strip() + '\n\n')  #题目+简介写进文件
    parapraghs = [i.text.strip() for i in soup.select('#js_content > p > span') if i.text.strip() != '' ]  #抓取段落列表并文本化,strip()去掉前后多余的空格
    for paragraph in parapraghs:
        if  '微信公众号' not in paragraph:      #判断本段是不是页末的广告
            f.write(paragraph.strip()+'\n\n')    #不是广告才写进去
        else:
            f.write('\n------本节完------'+'\n\n')    #到广告了写上"本节完"
            break
    return f

ssl._create_default_https_context = ssl._create_unverified_context    #weasyprint有时候强制要求ssl,但是有时候会抽风犯错,为了避免ssl证书出问题,我们禁用ssl

for title ,url, intro in all_list:
    print(f'正在整理文章:{title}')  #表明进度
    f = web2txt(f,url,intro)  #写txt,并依照题目命名
    HTML(url).write_pdf(os.path.join(outpathpdf,f'{title}.pdf')) #写pdf,并依照题目命名

f.close() #关闭文件对象,得到txt文件


#再将pdf合并输出

filelist = os.listdir(outpathpdf)   #读取文件夹里的文件名
pdfs = [ os.path.join(outpathpdf,file) for file in filelist if not os.path.isdir(file)]   #摘取文件里的pdf放进列表
pdfs.sort(key = lambda x : int(x.split('(')[1].split(')')[0])) #并按里面的数字排序,注意不能粗暴直接sort()排序,否则会出现10排在2前面的情况
print(pdfs)

#这段代码是直接合并pdf,不带书签的
'''
merger = PdfFileMerger()
for pdf in pdfs:
    merger.append(pdf)  #按pdf顺序合并
merger.write(os.path.join(outpath,'Table Tennis 24 Years.pdf'))  #合并输出
'''
#这段代码是逐页合并pdf,而且有超链接书签的
output  = PdfFileWriter()
output_Pages = 0  #文档总页数

for pdf in pdfs:
    input = PdfFileReader(open(pdf,'rb'))  #打开读取文档
    pdf_name = pdf.split('/')[-1]    #拿到文件名称作为书签名
    page_Count = input.getNumPages()    #读取当前小pdf的页数
    output_Pages += page_Count     #总页数累加计数
    for iPage in range(page_Count):
        output.addPage(input.getPage(iPage))   #小pdf内逐页添加到输出pdf对象
    output.addBookmark(pdf_name,pagenum =output_Pages-page_Count,parent=None)  #在小pdf的首页添加书签

output.write(open(os.path.join(outpath,'Table Tennis 24 Years.pdf'), 'wb'))     #合并输出

运行结果

  • txt
txt
  • pdf
pdf

踩过的坑

  • phantomJS

我一开始想要使用网页截图再转pdf,但是Webdriver的Chrome网页截图不支持滚动截图。其实selenium有两种形式,有头的和无头(headless)的,我用的是有头的浏览器,在以前开发者喜欢用的是PhantomJS,但是selenium不知道搞什么鬼,竟然在运行phantomJS时候提示最新版本的selenium停止支持js,建议我使用Chrome或者Firefox的无头浏览器,
无头浏览器就是没有界面的静默版本,也可以调用,但是肉眼看不见,不喜欢界面打扰的可以试试看下面的代码。

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
driver = webdriver.Chrome(executable_path='./chromedriver',chrome_options=chrome_options)
  • pdfkit

其实网页转pdf的库还有一个叫pdfkit,要预装wkhtmltopdf,而且转换效果很差,还不如这个库呢,不过weasyprint虽说效果更好,但是也是不支持异步加载的,有人在此项目的Github主页里issue了为什么不能加载微信文章的插图,作者也提到了这个问题,本库不支持js异步加载。

  • 爬取文章链接时的异步加载元素问题

在检测文章的入口的css元素样式的时候,如果点击了页上文章列表的新标签,那么在elements中所查找的css元素个数会增多,但是并不意味着你可以把列表标签挨个点击以后使用find_elements_by_css_selector方法一网打尽,你确实可以拿到元素,但是使用元素对象使用text方法以后,你发现只能从当前激活列表标签下的元素里拿出数据,不在当前页面的数据拿不出来,是空字符串'',所以只能点击一次拿一次。类似有人问过这样的问题,就是元素拿不到了。

因此,如果得到的文本只为空,那么当前定位的元素可能被隐藏了。

  1. 判断是否被隐藏 。 driver.find_element_by_xx().is_displayed() ,如果得到 false的结果.那就说明被隐藏了。
  2. 怎么解决is_displayed()为false的元素,依然可以通过getAttribute()方法获取元素的属性. 由于webdriver spec的定义,Selenium WebDriver 只会与可见元素交互,所以获取隐藏元素的文本总是会返回空字符串。可是,在某些情况下,我们需要获取隐藏元素的文本。这些内容可以使用element.attribute('attributeName'), 通过textContent, innerText, innerHTML等属性获取。
weasyprint.urls.URLFetchingError: 
URLError: <urlopen error [SSL:CERTIFICATE_VERIFY_FAILED] 
certificate verify failed (_ssl.c:777)

解决办法;禁用ssl

import ssl
ssl._create_default_https_context = ssl._create_unverified_context
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容