用 Python 将微信热文转换成Word文档 | 神级操作
不得不說微信公眾號已經成為了一個開放平臺,每天數以萬計的微信公眾號文章在這產生,我們關注一個微信公眾號每天便可以看到新的文章,我們同時也不知不覺的將好的文章分享到給朋友。
那么如何保存一個好的文章呢?普遍選擇收藏,然而在這里,我提供一個更巧妙的方法,直接轉換成word文檔保存在電腦里面。即便是以后文章404了,我們還可以看得到嘛。
1、微信熱文源代碼分析
一篇微信文章,url開頭一定是https://mp.weixin.qq.com/s/,后面跟著一長串字符串,比如qLjifoyinoVN5i5vjW0f7w。
查看網頁源代碼,我們發現
微信熱文的網頁源代碼很長,即便是上面的一個很簡短的文章,但我們要從中提取到我們想要的東西,比如
<h2id="activity-name">普京再次出面</h2>妥妥的文章題目,我們要把它保存為word文檔,題目肯定少不了。
<div id="js_profile_qrcode"class="profile_container" style="display:none;"><div><strong>環球時報</strong><imgid="js_profile_qrcode_img" src="" alt=""><p><label>微信號</label><spanclass="profile_meta_value">hqsbwx</span></p><p><label>功能介紹</label><span>報道多元世界 解讀復雜中國</span></p></div>這里一下子就提示了這篇文章是那個微信號發布的,而且還有微信號的介紹,這也是我們需要的信息
<div id="js_content" style="visibility: hidden;">這個就是正文的標簽了,這個標簽里面蘊含著正文,下面是正文的第一個標簽,我們將它格式化一下,如下
我們發現p套了很多層,但是實際上,這第一個標簽就這一句話是重點:“俄總統普京同納卡沖突雙方領導人舉行電話會談。”
下一個標簽也是p,但是涵蓋了好幾句話。我們發現了span標簽和strong標簽。而且出現了很多次rgb(),我們知道rgb是代表標簽內字體的顏色的。當然,strong是標簽內加粗咯。
<img data-ratio="0.7717391304347826" data-s="300,640"data-type="jpeg" data-w="828" data-backw="578"data-backh="446" src="https://mmbiz.qpic.cn/mmbiz_jpg/qkQTRn2Z9NwC8nNHScsBAFeOFtHHb95ftWKOZve0QJMqJPFtoicdYO8uTWom8fBdG07icCKDo0FoyNjHUyoBibI2g/640?wx_fmt=jpeg"style="text-align: center;width: 660.994px;box-sizing: border-box!important;visibility: visible !important;" />另一個圖片標簽
<img data-ratio="1.345"src="https://mmbiz.qpic.cn/mmbiz_gif/wlCrBZoK8HF5AE2ibhItnFJgoIQBcJhTzO438azQniaRJRYNFk0CzlORnm0g1hG7HX3bhXAIC1J4E2XGb1WKA4qA/640?wx_fmt=gif"data-type="gif" data-w="200" style="vertical-align:middle;box-sizing: border-box;" />這個是圖片的標簽,里面蘊含著很多重要的東西,比如,data-type="gif",表明這是一個gif文件,src指向了圖片的地址,data-w="200",代表圖片的寬度,這很重要。
格式化后的內容如下所示
標簽套標簽,讓人眼花繚亂。
不過,還是一步一步來吧。
2、設計代碼,步步分析
這一步我們需要開始編寫代碼了,python-docx是一個生成和處理docx的第三方庫,使用pip install python-docx 一鍵下載
需要用到的第三方庫有,python-docx,bs4(用于html解析處理)
from docx import Document from docx.oxml.ns import qn import re from docx.shared import RGBColor,Inches,Pt from urllib.request import urlopen,Request from bs4 import BeautifulSoup from docx.enum.text import WD_PARAGRAPH_ALIGNMENT import io from os.path import joinqingqiu={'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",'Accept':'text/plain, text/html'}編寫一個簡單的過濾函數,因為我們得到文章標題后,需要將文章標題中一些字符刪去,比如換行符,空格,以及{}?
/|\等字符,因為含有這些字符的字符串不能做文件名
def guolv(text):t = re.sub('\s', '', text)t = re.sub('[?<>()[\]{}|]', ':', t)return t假設微信url已經確定,在這里我們編寫一個類,這個類專門用來處理的。
class WX_doc():def __init__(self, url, path):self.img_num = 0self.doc = Document()self.doc.styles['Normal'].font.name = '微軟雅黑'self.doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), '微軟雅黑')self.url = urlself.path = pathself.img_num是針對img處理的,每處理一個img,self.img_num+=1,請注意,最好設置好文章的字體,因為python-docx默認字體顯示中文會比較難看……不信你可以去試試。當然也可以將字體設為宋體
url是指微信熱文的鏈接,path是Word文檔處理完后的保存路徑。
接下來是一個插入一個標題的方法。
注:
我們設單獨的def開頭的為函數,包含在class內的def開頭的為方法
def head(self, title, lv=3, size=13):p = self.doc.add_heading('', lv)p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTERr = p.add_run(title)r.font.name = '微軟雅黑'r.font.color.rgb = RGBColor(0, 0, 0)r.font.size = Pt(size)r._element.rPr.rFonts.set(qn('w:eastAsia'), u'微軟雅黑')將標題插入后,設置為居中,顏色黑色,大小默認13,字體微軟雅黑。
?
2、巧妙處理標簽
對于正文來講,標簽套標簽讓人眼花繚亂,然而我們如何處理正文中的文字,圖像甚至表格呢?
,對于標簽套標簽,我設計的思路是:
用對應的標簽方法處理標簽
hd = Request(self.url, headers=qingqiu) a = urlopen(hd) b = a.read() bb = b.decode('UTF-8') bs = BeautifulSoup(bb, 'lxml') h2 = bs.find('h2', {'class': "rich_media_title"}) title = guolv(h2.text) self.head(title, 2, 18)pingtai = bs.find('strong', {'class': "profile_nickname"}) PMV=bs.findAll('span',{'class':'profile_meta_value'}) p = self.doc.add_paragraph() r = p.add_run('%s' % pingtai.text) r.font.bold = True r.font.color.rgb = RGBColor(0, 191, 255) r.font.size = Pt(12) r=p.add_run('(%s: %s)'%(PMV[0].text,PMV[1].text)) r.font.size = Pt(9)wb = bs.find('div', {'class': "rich_media_content"})這樣一處理,bs就是整篇微信文章的BeautifulSoup結構的html,這樣處理就方便的多。
對于標題和發布者的,我們放到后面處理,現在要考慮正文的處理,wb就是正文的bs結構。
如何編寫標簽函數?我假定只關注字體的顏色和加粗,其余字體大小不考慮(這樣的話保存的文章樣式是一致的),使用RGB代表顏色,比如RGB=(0,0,0)就是純黑了,bold代表加粗,bold=True就是加粗。
<p>標簽
p代表段落,p標簽內的文字會形成一個段。對應doc中的add_paragraph方法,接下來我們編寫WX_doc的第一個標簽處理方法。默認字體顏色黑色,不加粗。
def para(self, label):p = self.doc.add_paragraph('')for i in label:self.transit(i,p, (0, 0, 0), False)這樣就完了,主要操作就是,將p中每一個標簽拿出來,交給transit函數處理,transit會針對相應的標簽交給相應的標簽方法。
但是如果出現這樣的情況,p內含p,就像p一樣一層套一層,那么需要另一個p處理方法
def para2(self,label,p,RGB,bold):"解決p內含p的情況"for i in label:self.transit(i,p, RGB, bold)對于后面的標簽處理方法,我們規定,需4個參數,第一個BeautifulSoup結構的標簽label,第二個,所屬的段落p,為doc.add_paragraph方法返回的段落p,第三個和第四個為RGB和bold。
<span>標簽
Span標簽出險率極高,基本上每段文字都會出現,我們假定span中的style設定文字的顏色。
比如這一段span
<spanstyle="letter-spacing: 1px;font-size: 16px;font-family: helvetica;color: rgb(123,12, 0);"><strong>普京與兩國領導人討論了本月9日三方簽訂的停火協議落實問題。各方對當前沖突接觸線的平靜局勢感到滿意。</strong></span>多次觀察后,編寫的處理方法如下
當BeautifulSoup結構下的標簽結構為None時,它就是一段純文字
?
Text 純文字處理
處理純文字用的方法,需要注意的是,要將文字中的換行符刪去。
def text(self, i, p, RGB, bold):i=str(i)i=i.replace('\n','')r = p.add_run(i)r.font.bold = boldr.font.color.rgb = RGBColor(RGB[0], RGB[1], RGB[2])<strong>標簽
Strong就是加粗
def strong(self, label, p, RGB, bold):for i in label:if i.name == None:self.text(i,p, RGB, True)elif i.name == 'span':self.span(i,p, RGB, True)<p>標簽
Section常常會出現套疊的情況,即便是里面有字體顏色大小的指示,我還是以span指示的顏色為準。那么如何正確處理p便是一個難題。
<pstyle="font-family: -apple-system-font, BlinkMacSystemFont, "HelveticaNeue", "PingFang SC", "Hiragino SansGB", "Microsoft YaHei UI", "MicrosoftYaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space:normal;background-color: rgb(255, 255, 255);line-height: 1.5em;margin-left:0px;margin-right: 0px;"><span style="color: rgb(136,136, 136);font-family: helvetica;font-size: 14px;font-weight:700;letter-spacing: 1px;text-align: left;text-indent: 28px;widows: 1;">▲</span><span style="color: rgb(136,136, 136);font-family: helvetica;font-size: 14px;font-weight:700;letter-spacing: 1px;text-align: left;text-indent: 28px;widows: 1;">俄總統網站聲明截圖</span></p>上面的p中出現了span,所以思路來了,遍歷p中的標簽,如果出現span和stong,直接按段落處理
def p(self,label):for i in label:if i.name=='p':self.para(i)elif i.name in ['span','strong']:self.para(label)return 0elif i.name=='p':self.p(i)elif i.name in ['ul','ol']:self.ul2(i)elif i.name=='img':self.img(i)elif i.name in ['br','svg','center']:passelif i.name=='blockquote':self.blockquote(i)elif i.name=='pre':self.pre(label)else:print('p中:%s:%s'%(i.name,str(i)))最后else表示沒有這個標簽的處理函數,就提示這個標簽的位置,以及名稱,所含內容
?
<img>標簽
我們知道img標簽是圖像,一篇文章加上圖像可謂畫龍點睛,現在很少出現沒有圖的文章了,正所謂有圖有真相。
以下面兩個圖像為例
<img data-ratio="0.7717391304347826"data-s="300,640" data-type="jpeg" data-w="828"data-backw="578" data-backh="446"src="https://mmbiz.qpic.cn/mmbiz_jpg/qkQTRn2Z9NwC8nNHScsBAFeOFtHHb95ftWKOZve0QJMqJPFtoicdYO8uTWom8fBdG07icCKDo0FoyNjHUyoBibI2g/640?wx_fmt=jpeg"style="text-align: center;width: 660.994px;box-sizing: border-box!important;visibility: visible !important;" /> <imgdata-ratio="1.345"src="https://mmbiz.qpic.cn/mmbiz_gif/wlCrBZoK8HF5AE2ibhItnFJgoIQBcJhTzO438azQniaRJRYNFk0CzlORnm0g1hG7HX3bhXAIC1J4E2XGb1WKA4qA/640?wx_fmt=gif"data-type="gif" data-w="200" style="vertical-align:middle;box-sizing: border-box;" />我們發現data-w是設定圖片的寬度,當圖片過大的時候,需要將圖片寬度設定好。Img處理函數如下
def img(self, label):src = label.attrs['src']da_s = label.attrs.get('data-s')data_type = label.attrs.get('data-type')data_w = label.attrs.get('data-w')self.img_num += 1a = urlopen(src)b = a.read()path = io.BytesIO(b)if da_s:num = re.findall('\d+', da_s)h = int(num[0]) // 75w = int(num[1]) // 75if w > 6:self.doc.add_picture(path, width=Inches(6))else:self.doc.add_picture(path, width=Inches(w), height=Inches(h))elif data_w:data_w = int(data_w)if data_w < 75:# 標簽太小,直接忽略print('忽略太小圖片%d.%s' % (self.img_num, data_type))elif data_w > 450:self.doc.add_picture(path, width=Inches(6))else:self.doc.add_picture(path, width=Inches(data_w / 75))else:self.doc.add_picture(path, width=Inches(6))print("圖片%d打入成功!" % (self.img_num - 1))transit方法
最后我們編寫transit方法
def transit(self, label, p, RGB, bold):"本函數提供label的中轉方案 其中br由中轉方案解決"if label.name == 'span':self.span(label, p,RGB,bold)elif label.name == None:self.text(label, p,RGB,bold)elif label.name in ['strong','em']:self.strong(label, p,RGB,bold)elif label.name=='p':self.p(label)elif label.name =='p':self.para2(label,p,RGB,bold)elif label.name == 'img':self.img(label)elif label.name in ['br','svg','mpcpc','center']:passelif label.name == 'a':self.link(label, p,RGB,bold)elif label.name == 'iframe':self.iframe(label, p)elif label.name == 'blockquote':self.blockquote(label)elif label.name == 'ul':self.ul(label, p)elif label.name=='pre':self.pre(label)else:print('p中:%s %s'%(str(label.name),str(label.text)))t = label.textif len(t) < 2:return 0r = p.add_run(t)r.font.bold = boldr.font.color.rgb = RGBColor(RGB[0], RGB[1], RGB[2])transit函數要處理一個標簽,如果已經編寫好了這個標簽方法,那么將這個標簽交給對應的標簽方法處理,如果沒有,就提示這個標簽的位置,以及名稱,所含內容
?
main 核心處理
最后我們當然是處理并且轉換成文檔啦,加入文章標題,發布者,和內容,直接發完整代碼吧,如下:
def main(self) -> None:hd = Request(self.url, headers=qingqiu)a = urlopen(hd)b = a.read()bb = b.decode('UTF-8')bs = BeautifulSoup(bb, 'lxml')h2 = bs.find('h2', {'class': "rich_media_title"})title = guolv(h2.text)self.head(title, 2, 18)pingtai = bs.find('strong', {'class': "profile_nickname"})PMV=bs.findAll('span',{'class':'profile_meta_value'})p = self.doc.add_paragraph()r = p.add_run('%s' % pingtai.text)r.font.bold = Truer.font.color.rgb = RGBColor(0, 191, 255)r.font.size = Pt(12)r=p.add_run('(%s: %s)'%(PMV[0].text,PMV[1].text))r.font.size = Pt(9)wb = bs.find('div', {'class': "rich_media_content"})for i in wb:if i.name =='p':self.para(i)elif i.name=='p':self.p(i)elif i.name == 'blockquote':self.blockquote(i)elif i.name == 'table':self.table(i)elif i.name in[None,'center','hr']:passelif i.name in ['h1', 'h2', 'h3','h4']:self.head(i.text, int(i.name[1]) + 1)elif i.name in ['ul','ol']:self.ul2(i)elif i.name == 'pre':self.pre(i)else:print("%s"%str(i))self.save_docx(title)wz_pa=join(self.path,title+'.docx')print('文檔保存成功!保存路徑:%s'%wz_pa)self.ok=Falseprint(wz_pa)3、實戰測試
運行后輸入微信url,結果如下:
保存下來的Word文檔如下:
4、其他標簽的處理說明
剛剛我們僅僅是編寫了p,span,p,strong等標簽,就可以對付一個簡單的文章,但是實際上還有其他的標簽,僅僅是這篇文章沒出現而已。所以為了讓這程序越來越好,我們需要添加一些標簽處理的方法。
<blockquote>標簽
Blockquote代表著引用,比如文章引用的哪句話,抄了哪些文獻的句子,都用這個標簽。為了和正文區別,我將字體大小設置為9默認顏色(100,100,100)
def blockquote(self, label):"定義一個摘自另一個源的塊引用"p = self.doc.add_paragraph('')p.style.font.size = Pt(9)for i in label:self.transit(i,p,(100,100,100),False)?
<iframe>標簽
iframe標簽出現代表著這篇微信熱文嵌入了一個視頻。
def iframe(self, label, p):t = '\n' + '*' * 20 + '\n一個視頻\n鏈接是:%s\n' % label.attrs['src'] + '*' * 20 + '\n'r = p.add_run(t)r.font.size = Pt(10)print('發現一個視頻,文檔只能留下鏈接')<ul>和<ol>標簽
出現ul和ol是列舉,比如列舉1.…… 2.……,有兩套方法,如果ul和ol是在<p>標簽內,那么需要將它寫在這個段落里面,使用ul,如果單獨出現,使用ul2。
def ul(self, label, p):"零個或更多個 <li> 元素,可以混合使用 <ol> 與<ul> 元素。"lis = label.findAll('li')for i in lis:t = i.textr = p.add_run(" ★ %s\n" % t)r.font.size = Pt(9)def ul2(self, label):p = self.doc.add_paragraph()lis = label.findAll('li')for i in lis:t = i.textr = p.add_run("● %s\n" % t)r.font.size = Pt(9)<a>標簽
<a>是鏈接,如果微信文章出現鏈接,轉換為Word文檔需要特殊一下,加上下劃線,附上鏈接的url
def link(self, label, p, RGB, bold):"就是標簽a"r = p.add_run(label.text)r.font.underline = Truer.font.color.rgb = RGBColor(0,0,139)r.font.bold = boldhref=label.attrs['href']r=p.add_run("(%s)"%href)r.font.color.rgb = RGBColor(135,206,250)r.font.size=Pt(9)r.font.underline = True<table>標簽
Table是表格,當出現這個的時候,就需要添加表格啦,這個方法只適合整齊的表格,有合并的無效
def table(self, label):"只適合整齊的表格,對于不整齊的(就是有合并)無效"pave = {'color': (0, 0, 0), 'bold': False}tr = label.findAll('tr')td = label.findAll('td')row = len(tr)col = len(td) // len(tr)if len(td) % len(tr) != 0:col += 1del tdtab = self.doc.add_table(rows=row, cols=col, style='Table Grid')for i in range(row):tdlb = tr[i].findAll('td')for j in range(col):td = tdlb[j]dqcell = tab.cell(i, j)p = dqcell.paragraphs[0]for nr in td:if nr.name == 'p':for nrr in nr:self.transit(nrr, p, (0, 0, 0), False)else:self.transit(nr, p, (0, 0, 0), False)<pre>和<code>標簽
Pre標簽和code經常出現在一起,如果pre內含code,那么就是代碼行了,交給code函數,如果pre單獨出現,直接按照段落處理。Code處理,就是將文字,啊不是,是將代碼框入到一個表格中,文字大小9,以示區別。
def pre(self,label):"pre分兩種情況考慮,內嵌代碼行和普通pre"code = label.findAll('code')if code:for i in code:self.code(i)else:self.para(label)def code(self, label):"特殊標簽,用于代碼行"RGB = (0, 0, 0)bold = Falsetab = self.doc.add_table(rows=1, cols=1, style='Table Grid')p = tab.cell(0, 0).paragraphs[0]p.style.font.size = Pt(10)for i in label:if i.name == 'br':p.add_run('\n')elif i.name == 'span':self.span(i, p, RGB, bold)elif i.name == None:p.add_run(str(i))加上了其他標簽處理方法,那么我們需要將p、transit和main主函數修改一下了,加上對應的標簽處理語句。
5、總結
1.? 對于圖像,gif動圖導入Word文檔后不會播放
2.? 標簽分類如下
核心標簽:<p>,<strong>,<p>,<img>,<span>,<h1>,<h2>,<h3>……(這些標簽可以用head方法處理)
其他標簽:<blockquote>,<iframe>,<ul>和<ol>,<a>,<table>,<pre>,<code>……
忽略標簽:<br>,<hr>,<mpcpc>(微信廣告投放點),<center>,<svg>
3.? 編寫其他標簽函數,如果你覺得新的標簽方法需要增加,只需要加入新的標簽方法,并且在transit和p以及main中加入它即可
4.? 個性化設置你覺得合理的標簽方法,只需改動標簽方法源代碼即可
往期推薦: 收藏 | 49 個 Python 學習資源我都逛哪些技術網站?(程序員必備58個網站匯總)肝!精心整理了 50 個數據源網站!總結
以上是生活随笔為你收集整理的用 Python 将微信热文转换成Word文档 | 神级操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何使用 sklearn 优雅地进行数据
- 下一篇: 一张截图,告诉你字节跳动的 Java 开