新聞中心
不得不說微信公眾號(hào)已經(jīng)成為了一個(gè)開放平臺(tái),每天數(shù)以萬計(jì)的微信公眾號(hào)文章在這產(chǎn)生,我們關(guān)注一個(gè)微信公眾號(hào)每天便可以看到新的文章,我們同時(shí)也不知不覺的將好的文章分享到給朋友。

那么如何保存一個(gè)好的文章呢?普遍選擇收藏,然而在這里,我提供一個(gè)更巧妙的方法,直接轉(zhuǎn)換成word文檔保存在電腦里面。即便是以后文章404了,我們還可以看得到嘛。
1、微信熱文源代碼分析
一篇微信文章,url開頭一定是https://mp.weixin.qq.com/s/,后面跟著一長(zhǎng)串字符串,比如qLjifoyinoVN5i5vjW0f7w。
查看網(wǎng)頁源代碼,我們發(fā)現(xiàn)
微信熱文的網(wǎng)頁源代碼很長(zhǎng),即便是上面的一個(gè)很簡(jiǎn)短的文章,但我們要從中提取到我們想要的東西,比如
普京再次出面
妥妥的文章題目,我們要把它保存為word文檔,題目肯定少不了。
- 環(huán)球時(shí)報(bào)
hqsbwx
- 報(bào)道多元世界 解讀復(fù)雜中國(guó)
這里一下子就提示了這篇文章是那個(gè)微信號(hào)發(fā)布的,而且還有微信號(hào)的介紹,這也是我們需要的信息
這個(gè)就是正文的標(biāo)簽了,這個(gè)標(biāo)簽里面蘊(yùn)含著正文,下面是正文的第一個(gè)標(biāo)簽,我們將它格式化一下,如下
我們發(fā)現(xiàn)section套了很多層,但是實(shí)際上,這第一個(gè)標(biāo)簽就這一句話是重點(diǎn):“俄總統(tǒng)普京同納卡沖突雙方領(lǐng)導(dǎo)人舉行電話會(huì)談?!?/p>
下一個(gè)標(biāo)簽也是section,但是涵蓋了好幾句話。我們發(fā)現(xiàn)了span標(biāo)簽和strong標(biāo)簽。而且出現(xiàn)了很多次rgb(),我們知道rgb是代表標(biāo)簽內(nèi)字體的顏色的。當(dāng)然,strong是標(biāo)簽內(nèi)加粗咯。
另一個(gè)圖片標(biāo)簽
這個(gè)是圖片的標(biāo)簽,里面蘊(yùn)含著很多重要的東西,比如,data-type="gif",表明這是一個(gè)gif文件,src指向了圖片的地址,data-w="200",代表圖片的寬度,這很重要。
格式化后的內(nèi)容如下所示
標(biāo)簽套標(biāo)簽,讓人眼花繚亂。
不過,還是一步一步來吧。
2、設(shè)計(jì)代碼,步步分析
這一步我們需要開始編寫代碼了,python-docx是一個(gè)生成和處理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 join
- qingqiu={'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'
- }
編寫一個(gè)簡(jiǎn)單的過濾函數(shù),因?yàn)槲覀兊玫轿恼聵?biāo)題后,需要將文章標(biāo)題中一些字符刪去,比如換行符,空格,以及{}?
/|\等字符,因?yàn)楹羞@些字符的字符串不能做文件名
- def guolv(text):
- t = re.sub('\s', '', text)
- t = re.sub('[?<>()[\]{}|]', ':', t)
- return t
假設(shè)微信url已經(jīng)確定,在這里我們編寫一個(gè)類,這個(gè)類專門用來處理的。
- class WX_doc():
- def __init__(self, url, path):
- self.img_num = 0
- self.doc = Document()
- self.doc.styles['Normal'].font.name = '微軟雅黑'
- self.doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), '微軟雅黑')
- self.url = url
- self.path = path
self.img_num是針對(duì)img處理的,每處理一個(gè)img,self.img_num+=1,請(qǐng)注意,最好設(shè)置好文章的字體,因?yàn)閜ython-docx默認(rèn)字體顯示中文會(huì)比較難看……不信你可以去試試。當(dāng)然也可以將字體設(shè)為宋體
url是指微信熱文的鏈接,path是Word文檔處理完后的保存路徑。
接下來是一個(gè)插入一個(gè)標(biāo)題的方法。
注:
我們?cè)O(shè)單獨(dú)的def開頭的為函數(shù),包含在class內(nèi)的def開頭的為方法
- def head(self, title, lv=3, size=13):
- p = self.doc.add_heading('', lv)
- p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
- r = 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'微軟雅黑')
將標(biāo)題插入后,設(shè)置為居中,顏色黑色,大小默認(rèn)13,字體微軟雅黑。
2、巧妙處理標(biāo)簽
對(duì)于正文來講,標(biāo)簽套標(biāo)簽讓人眼花繚亂,然而我們?nèi)绾翁幚碚闹械奈淖郑瑘D像甚至表格呢?
,對(duì)于標(biāo)簽套標(biāo)簽,我設(shè)計(jì)的思路是:
用對(duì)應(yīng)的標(biāo)簽方法處理標(biāo)簽
- hd = Request(self.url, headers=qingqiu)
- a = urlopen(hd)
- b = a.read()
- bbb = 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結(jié)構(gòu)的html,這樣處理就方便的多。
對(duì)于標(biāo)題和發(fā)布者的,我們放到后面處理,現(xiàn)在要考慮正文的處理,wb就是正文的bs結(jié)構(gòu)。
如何編寫標(biāo)簽函數(shù)?我假定只關(guān)注字體的顏色和加粗,其余字體大小不考慮(這樣的話保存的文章樣式是一致的),使用RGB代表顏色,比如RGB=(0,0,0)就是純黑了,bold代表加粗,bold=True就是加粗。
標(biāo)簽
p代表段落,p標(biāo)簽內(nèi)的文字會(huì)形成一個(gè)段。對(duì)應(yīng)doc中的add_paragraph方法,接下來我們編寫WX_doc的第一個(gè)標(biāo)簽處理方法。默認(rèn)字體顏色黑色,不加粗。
- def para(self, label):
- p = self.doc.add_paragraph('')
- for i in label:
- self.transit(i,p, (0, 0, 0), False)
這樣就完了,主要操作就是,將p中每一個(gè)標(biāo)簽?zāi)贸鰜恚唤otransit函數(shù)處理,transit會(huì)針對(duì)相應(yīng)的標(biāo)簽交給相應(yīng)的標(biāo)簽方法。
但是如果出現(xiàn)這樣的情況,p內(nèi)含p,就像section一樣一層套一層,那么需要另一個(gè)p處理方法
- def para2(self,label,p,RGB,bold):
- "解決p內(nèi)含p的情況"
- for i in label:
- self.transit(i,p, RGB, bold)
對(duì)于后面的標(biāo)簽處理方法,我們規(guī)定,需4個(gè)參數(shù),第一個(gè)BeautifulSoup結(jié)構(gòu)的標(biāo)簽label,第二個(gè),所屬的段落p,為doc.add_paragraph方法返回的段落p,第三個(gè)和第四個(gè)為RGB和bold。
標(biāo)簽
Span標(biāo)簽出險(xiǎn)率極高,基本上每段文字都會(huì)出現(xiàn),我們假定span中的style設(shè)定文字的顏色。
比如這一段span
普京與兩國(guó)領(lǐng)導(dǎo)人討論了本月9日三方簽訂的停火協(xié)議落實(shí)問題。各方對(duì)當(dāng)前沖突接觸線的平靜局勢(shì)感到滿意。 多次觀察后,編寫的處理方法如下
- def span(self, label, p, RGB, bold):
- attr = label.attrs.get('style')
- if attr:
- ys = re.findall('(?<=rgb\()[\s\S]+?(?=\))', attr)
- else:
- ys=[]
- if ys:
- rgb = re.findall('\d+', ys[0])
- r = int(rgb[0])
- g = int(rgb[1])
- b = int(rgb[2])
- RGB = (r, g, b)
- for i in label:
- if i.name == None:
- self.text(i,p, RGB, bold)
- elif i.name == "strong":
- self.strong(i,p, RGB, bold)
- else:
- self.transit(i,p, RGB, bold)
當(dāng)BeautifulSoup結(jié)構(gòu)下的標(biāo)簽結(jié)構(gòu)為None時(shí),它就是一段純文字
Text 純文字處理
處理純文字用的方法,需要注意的是,要將文字中的換行符刪去。
- def text(self, i, p, RGB, bold):
- i=str(i)
- ii=i.replace('\n','')
- r = p.add_run(i)
- r.font.bold = bold
- r.font.color.rgb = RGBColor(RGB[0], RGB[1], RGB[2])
標(biāo)簽
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)
標(biāo)簽 Section常常會(huì)出現(xiàn)套疊的情況,即便是里面有字體顏色大小的指示,我還是以span指示的顏色為準(zhǔn)。那么如何正確處理section便是一個(gè)難題。
- ▲
- 俄總統(tǒng)網(wǎng)站聲明截圖
上面的section中出現(xiàn)了span,所以思路來了,遍歷section中的標(biāo)簽,如果出現(xiàn)span和stong,直接按段落處理
- def section(self,label):
- for i in label:
- if i.name=='p':
- self.para(i)
- elif i.name in ['span','strong']:
- self.para(label)
- return 0
- elif i.name=='section':
- self.section(i)
- elif i.name in ['ul','ol']:
- self.ul2(i)
- elif i.name=='img':
- self.img(i)
- elif i.name in ['br','svg','center']:
- pass
- elif i.name=='blockquote':
- self.blockquote(i)
- elif i.name=='pre':
- self.pre(label)
- else:
- print('section中:%s:%s'%(i.name,str(i)))
最后else表示沒有這個(gè)標(biāo)簽的處理函數(shù),就提示這個(gè)標(biāo)簽的位置,以及名稱,所含內(nèi)容
標(biāo)簽
我們知道img標(biāo)簽是圖像,一篇文章加上圖像可謂畫龍點(diǎn)睛,現(xiàn)在很少出現(xiàn)沒有圖的文章了,正所謂有圖有真相。
以下面兩個(gè)圖像為例
我們發(fā)現(xiàn)data-w是設(shè)定圖片的寬度,當(dāng)圖片過大的時(shí)候,需要將圖片寬度設(shè)定好。Img處理函數(shù)如下
- 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 += 1
- a = urlopen(src)
- b = a.read()
- path = io.BytesIO(b)
- if da_s:
- num = re.findall('\d+', da_s)
- h = int(num[0]) // 75
- w = int(num[1]) // 75
- if 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:
- # 標(biāo)簽太小,直接忽略
- 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):
- "本函數(shù)提供label的中轉(zhuǎn)方案 其中br由中轉(zhuǎn)方案解決"
- 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=='section':
- self.section(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']:
- pass
- elif 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.text
- if len(t) < 2:
- return 0
- r = p.add_run(t)
- r.font.bold = bold
- r.font.color.rgb = RGBColor(RGB[0], RGB[1], RGB[2])
transit函數(shù)要處理一個(gè)標(biāo)簽,如果已經(jīng)編寫好了這個(gè)標(biāo)簽方法,那么將這個(gè)標(biāo)簽交給對(duì)應(yīng)的標(biāo)簽方法處理,如果沒有,就提示這個(gè)標(biāo)簽的位置,以及名稱,所含內(nèi)容
main 核心處理
最后我們當(dāng)然是處理并且轉(zhuǎn)換成文檔啦,加入文章標(biāo)題,發(fā)布者,和內(nèi)容,直接發(fā)完整代碼吧,如下:
- def main(self) -> None:
- hd = Request(self.url, headers=qingqiu)
- a = urlopen(hd)
- b = a.read()
- bbb = 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"})
- for i in wb:
- if i.name =='p':
- self.para(i)
- elif i.name=='section':
- self.section(i)
- elif i.name == 'blockquote':
- self.blockquote(i)
- elif i.name == 'table':
- self.table(i)
- elif i.name in[None,'center','hr']:
- pass
- elif 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=False
- print(wz_pa)
3、實(shí)戰(zhàn)測(cè)試
運(yùn)行后輸入微信url,結(jié)果如下:
保存下來的Word文檔如下:
4、其他標(biāo)簽的處理說明
剛剛我們僅僅是編寫了section,span,p,strong等標(biāo)簽,就可以對(duì)付一個(gè)簡(jiǎn)單的文章,但是實(shí)際上還有其他的標(biāo)簽,僅僅是這篇文章沒出現(xiàn)而已。所以為了讓這程序越來越好,我們需要添加一些標(biāo)簽處理的方法。
標(biāo)簽Blockquote代表著引用,比如文章引用的哪句話,抄了哪些文獻(xiàn)的句子,都用這個(gè)標(biāo)簽。為了和正文區(qū)別,我將字體大小設(shè)置為9默認(rèn)顏色(100,100,100)
- def blockquote(self, label):
- "定義一個(gè)摘自另一個(gè)源的塊引用"
- p = self.doc.add_paragraph('')
- p.style.font.size = Pt(9)
- for i in label:
- self.transit(i,p,(100,100,100),False)
iframe標(biāo)簽出現(xiàn)代表著這篇微信熱文嵌入了一個(gè)視頻。
- def iframe(self, label, p):
- t = '\n' + '*' * 20 + '\n一個(gè)視頻\n鏈接是:%s\n' % label.attrs['src'] + '*' * 20 + '\n'
- r = p.add_run(t)
- r.font.size = Pt(10)
- print('發(fā)現(xiàn)一個(gè)視頻,文檔只能留下鏈接')
和
標(biāo)簽
出現(xiàn)ul和ol是列舉,比如列舉1.…… 2.……,有兩套方法,如果ul和ol是在
標(biāo)簽內(nèi),那么需要將它寫在這個(gè)段落里面,使用ul,如果單獨(dú)出現(xiàn),使用ul2。
- def ul(self, label, p):
- "零個(gè)或更多個(gè)
- 元素,可以混合使用
與
元素。"
- lis = label.findAll('li')
- for i in lis:
- t = i.text
- r = 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.text
- r = p.add_run("● %s\n" % t)
- r.font.size = Pt(9)
是鏈接,如果微信文章出現(xiàn)鏈接,轉(zhuǎn)換為Word文檔需要特殊一下,加上下劃線,附上鏈接的url
- def link(self, label, p, RGB, bold):
- "就是標(biāo)簽a"
- r = p.add_run(label.text)
- r.font.underline = True
- r.font.color.rgb = RGBColor(0,0,139)
- r.font.bold = bold
- href=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
標(biāo)簽
Table是表格,當(dāng)出現(xiàn)這個(gè)的時(shí)候,就需要添加表格啦,這個(gè)方法只適合整齊的表格,有合并的無效
- def table(self, label):
- "只適合整齊的表格,對(duì)于不整齊的(就是有合并)無效"
- 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 += 1
- del td
- tab = self.doc.add_table(rowrows=row, colcols=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)
和標(biāo)簽Pre標(biāo)簽和code經(jīng)常出現(xiàn)在一起,如果pre內(nèi)含code,那么就是代碼行了,交給code函數(shù),如果pre單獨(dú)出現(xiàn),直接按照段落處理。Code處理,就是將文字,啊不是,是將代碼框入到一個(gè)表格中,文字大小9,以示區(qū)別。
- def pre(self,label):
- "pre分兩種情況考慮,內(nèi)嵌代碼行和普通pre"
- code = label.findAll('code')
- if code:
- for i in code:
- self.code(i)
- else:
- self.para(label)
- def code(self, label):
- "特殊標(biāo)簽,用于代碼行"
- RGB = (0, 0, 0)
- bold = False
- tab = 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))
加上了其他標(biāo)簽處理方法,那么我們需要將section、transit和main主函數(shù)修改一下了,加上對(duì)應(yīng)的標(biāo)簽處理語句。
5、總結(jié)
1. 對(duì)于圖像,gif動(dòng)圖導(dǎo)入Word文檔后不會(huì)播放
2. 標(biāo)簽分類如下
核心標(biāo)簽:
,,
, ,,
,
,
……(這些標(biāo)簽可以用head方法處理)
其他標(biāo)簽:
,


咨詢
建站咨詢