【日常】寫給媽媽的微信機器人(用於知網論文下載)

最近媽媽工作需求突然要寫論文,我長這麼大第一次聽說就她還能寫論文。可惜我不是學醫的,這方面跨得太大基本上無能爲力,最多給她潤色一下。她那邊下論文又不方便,我當天連夜趕了一份微信機器人出來,供她下載論文。

實現邏輯非常簡單,微信端用itchat,調用之前就編寫好的類CNKI,稍加修改就可以滿足媽媽在微信上與我交互,我這邊可以自動向她展示知網搜索結果,翻頁,查看簡介,及下載論文再通過微信發送給她。

典型的類前後端架構,前端用itchat編寫,代碼如下:

前端

基本上考慮了所有的業務邏輯,我簡化了一下,就是下載完論文自動退出系統(否則流程分叉太多我覺得也不是很好寫);

#-*- coding:UTF-8 -*- 
import re
import os
import sys
sys.path.append(r"F:\Programming Software\Python\Project\Project_16(CNKI)")
sys.path.append(r"C:\Users\lenovo\Downloads")

import time
import itchat
from itchat.content import TEXT
from CNKI import CNKI



import json
import numpy
import pandas
import random

from PIL import Image
from requests import Session
from bs4 import BeautifulSoup
from selenium import webdriver
from matplotlib import pyplot as plt
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains


""" 設置自動回覆 """

global cnki, dialcount, flag1, flag2, flag3, flag4, judge1, judge2, judge3, judge4, page, driver, results, URL

dialcount = 0
flag1 = True															 # 下一句話是搜索?
flag2 = False															 # 下一句話是看簡介?
flag3 = False															 # 下一句話是要下載論文?
flag4 = False															 # 下一句話是要翻頁?
judge1 = False															 # 下一句話是搜索完後的一個判斷?
judge2 = False															 # 下一句話是看完簡介後的一個判斷?
judge3 = False															 # 下一句話是下載完後的一個判斷?
judge4 = False															 # 下一句話是搜索完後的一個判斷?
driver = None
results = None
page = 1																 # 目前在第幾頁: 處理翻頁
URL = None																 # 記錄當前所在URL

string1 = "歡迎使用簡化版中國知網系統,請輸入你需要查詢的關鍵詞!任何時候只要你輸入'退出'即可退出當前會話,系統將會重置,你之後可以隨時開始一次新的會話。會話中請根據引導信息進行文字輸入操作"
string2 = "上面是第{}頁的論文信息\n如果需要查看下一頁請輸入1\n如果重新搜索請輸入2\n如果需要查看簡介請輸入'a+<序號>'\n如果需要下載論文請輸入'b+<序號>'".format(page)
string3 = "該論文沒有PDF下載選項,如需要下載轉人工服務"
string4 = "系統內部邏輯錯誤,請聯繫管理員調試代碼"

@itchat.msg_register(TEXT,isFriendChat=True,isGroupChat=False,isMpChat=False)
def text_reply(msg):
	global cnki, dialcount, flag1, flag2, flag3, flag4, judge1, judge2, judge3, judge4, page, driver, results, URL
	#itchat.send_msg("[%s]收到好友@%s 的信息:%s\n" %(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(msg["CreateTime"])),msg["User"]["NickName"], msg["Text"]), "filehelper")

	
	if msg["User"]["NickName"]=="媽媽":
		string = msg["Text"]
		if dialcount==0:
			dialcount = 1
			itchat.send_msg("正在啓動系統,這可能需要一段時間...",toUserName=msg["FromUserName"])
			cnki = CNKI()												 # 新建類
			return string1

		elif string=="退出": 											 # 重置會話
			try:
				dialcount = 0
				flag1 = True											 # 下一句話是搜索?
				flag2 = False											 # 下一句話是看簡介?
				flag3 = False											 # 下一句話是要下載論文?
				flag4 = False											 # 下一句話是要翻頁?
				judge1 = False											 # 下一句話是搜索完後的一個判斷?
				judge2 = False											 # 下一句話是看完簡介後的一個判斷?
				judge3 = False											 # 下一句話是搜索完後的一個判斷?
				judge4 = False											 # 下一句話是搜索完後的一個判斷?
				driver = None
				results = None
				page = 1
				URL = None
				driver.quit()
			except:
				pass
			return "感謝您的使用,歡迎下次再來!" 

		else:
			if flag1:													 # 處理搜索
				results = cnki.search(string)
				for i in range(len(results["序號"])):
					itchat.send_msg("序號{}\n{}".format(i+1,results["題名"][i]),toUserName=msg["FromUserName"])
				judge1 = True
				flag1 = False
				page = 1
				return string2

			elif judge1:												 # 處理搜索後的選擇
				if string==1:											 # 下一頁?說實話我害怕driver過期就很麻煩
					results = cnki.search(string,page=page+1,headless=True)	 # 查詢下一頁
					for i in range(len(results["序號"])):
						itchat.send_msg("序號{}\n{}".format(i+1,results["題名"][i]),toUserName=msg["FromUserName"])
					judge1 = True										 # 仍然是處於搜索後選擇的狀態
					flag1 = False										 # 可以省略
					page += 1
					return string2
					
				elif string==2:											 # 重新搜索
					judge1 = False										 # 取消judge1
					flag1 = True										 # 重新進入搜索
					page = 1											 # 重置page
					return "搜索引擎已重置,請重新輸入關鍵詞!"

				elif string[0]=="a":									 # 查看簡介
					try: index = int(string[1:].strip())
					except: return "序號無法轉化爲整數型, 請重新輸入!"
					if index<=20 and index>=1:
						URL = results["鏈接"][index-1]
						response = cnki.search_for_details(URL)
						itchat.send_msg("{}的簡介如下:\n".format(results["題名"][index-1],response),toUserName=msg["FromUserName"])
						judge1 = False									 # 取消judge1
						judge2 = True									 # 進入judge2
						return "簡介如上!如需下載該論文請輸入1, 如需翻頁請按2, 重新搜索請按3"
					else:
						return "序號不在範圍內(1~20), 請重新輸入!"

				elif string[0]=="b":									 # 下載文件
					try: index = int(string[1:].strip())
					except: return "序號無法轉化爲整數型, 請重新輸入!"
					if index<=20 and index>=1:
						flag = cnki.download(results["鏈接"][index-1])	 # 下論文了唄
						if flag==0:
							itchat.send_msg(string3,toUserName=msg["FromUserName"])
							return "請重新輸入您要進行的操作"
						elif flag==1:
							itchat.send_msg(string4,toUserName=msg["FromUserName"])
							return "請重新輸入您要進行的操作"
						else:
							itchat.send_file("F:\Temp\{}.pdf".format(flag),toUserName=msg["FromUserName"])
							try:
								dialcount = 0
								flag1 = True											 # 下一句話是搜索?
								flag2 = False											 # 下一句話是看簡介?
								flag3 = False											 # 下一句話是要下載論文?
								flag4 = False											 # 下一句話是要翻頁?
								judge1 = False											 # 下一句話是搜索完後的一個判斷?
								judge2 = False											 # 下一句話是看完簡介後的一個判斷?
								judge3 = False											 # 下一句話是搜索完後的一個判斷?
								judge4 = False											 # 下一句話是搜索完後的一個判斷?
								driver = None
								results = None
								page = 1
								URL = None
								driver.quit()
							except:
								pass
							return "下載完畢!系統已退出!"
					else:
						return "序號不在範圍內(1~20), 請重新輸入!"							

			elif judge2:												 # 查看簡介後的judge
				if string=="1":
					flag = cnki.download(URL)	 						 # 下論文了唄
					if flag==0:
						itchat.send_msg(string3,toUserName=msg["FromUserName"])
						return "請重新輸入您要進行的操作"
					elif flag==1:
						itchat.send_msg(string4,toUserName=msg["FromUserName"])
						return "請重新輸入您要進行的操作"
					else:
						itchat.send_file("F:\Temp\{}.pdf".format(flag),toUserName=msg["FromUserName"])
						try:
							dialcount = 0
							flag1 = True								 # 下一句話是搜索?
							flag2 = False								 # 下一句話是看簡介?
							flag3 = False								 # 下一句話是要下載論文?
							flag4 = False								 # 下一句話是要翻頁?
							judge1 = False								 # 下一句話是搜索完後的一個判斷?
							judge2 = False								 # 下一句話是看完簡介後的一個判斷?
							judge3 = False								 # 下一句話是搜索完後的一個判斷?
							judge4 = False								 # 下一句話是搜索完後的一個判斷?
							driver = None
							results = None
							page = 1
							URL = None
							driver.quit()
						except:
							pass
						return "下載完畢!系統已退出!"
					pass
				elif string=="2":										 # 翻頁
					results = cnki.search(string,page=page+1,headless=True)			 # 查詢下一頁
					for i in range(len(results["序號"])):
						itchat.send_msg("序號{}\n{}".format(i+1,results["題名"][i]),toUserName=msg["FromUserName"])
					judge1 = True										 # 仍然是處於搜索後選擇的狀態
					judge2 = False										 # 可以省略
					page += 1
					return string2
					
				elif string=="3":										 # 重新搜索
					judge1 = False										 # 取消judge1
					flag1 = True										 # 重新進入搜索
					page = 1											 # 重置page
					return "搜索引擎已重置,請重新輸入關鍵詞!"					

				else:
					return "您輸入的不是1,2,3,請重新輸入"
			
if __name__ == "__main__":
	itchat.auto_login()
	itchat.run()

 

後端

後端主要就是CNKI類,被前端調用,這裏我也不說太多,因爲代碼基本上也很難在其他計算機上跑起來,我寫的比較硬,因爲趕時間,媽媽急着要,我就記幾個坑:

知網的搜索結果是被iframe給封裝了,所以用selenium時要用switch_to_frame方法先進入iframe才能拿到搜索結果的html;說實話這個問題坑了我很長時間,因爲我一直搞不清楚爲什麼不能獲取到搜索結果的html,明明元素都能定位的到;

知網的搜索結果中的論文題目的<a>標籤中的href超鏈接地址是沒有用的,點進去直接返回知網首頁(比如你右鍵這個論文題目的超鏈接,選擇打開鏈接,將直接返回知網首頁)會重定向應該是,我看了一下只有URLID之後那串神祕代碼是有用的,你們可以自己看看,每個論文所在

看下面這張圖URL裏框出來的部分對比上面這張圖的框出來的<a>標籤中的href中的URLID字段後面的神祕代碼,它們是一樣的,事實上蝦米那這張圖URL中問號"?"後面的部分(即uid=...,這個uid我試過是動態生成的,我就不塗掉了)是沒用的,直接刪了用requests庫訪問這個URL就可以拿到這個頁面的信息(簡介在這個頁面上)。不過問題是好像並非所有的論文都有這個URLID,優的DBCODE(比如你搜索"中國知網"得到的論文超鏈接結構就不一樣了)。這可能是知網數據庫自身架構的不同,這個也沒有什麼辦法,selenium直接模擬點擊的話應該也可以(爲什麼我說應該,因爲我沒成功。。。但按理說應該可以,我出的問題是拿不到點擊後的頁面的URL,應該可以解決),無視一切花裏胡哨的噱頭。

完事點擊進來就下載PDF咯,媽媽太笨,只能給她全部弄好,CAJ又不會看,只能把PDF打印出來咯。這裏因爲下載會有彈窗,我想的辦法是用pykeyboard模擬敲擊ENTER鍵下載。我看到的辦法還有就是對webdriver進行set_preference,但是我發現無論我怎麼設定,webdriver都沒有變化(比如設定默認下載路徑,禁止彈窗之類的),無奈只能讓它下到默認路徑再剪切到我要的路徑上來了。。。

		self.profile = webdriver.FirefoxProfile()
		profile.set_preference("browser.download.folderList", 2)
		profile.set_preference("browser.download.dir", "d:\\tmp") # 下載路徑
		option.set_preference("dom.webnotifications.enabled",False) # 不彈窗(指一個窗口只能有一個標籤頁)
		browser = webdriver.Firefox(option)

給個CNKI的類吧,可能會跑不起來,反正我能跑就完事了。爬蟲還是要自己寫的

#-*- coding:UTF-8 -*-
import re
import os
import sys
import time
import json
import numpy
import pandas
import random

from PIL import Image
from requests import Session
from bs4 import BeautifulSoup
from selenium import webdriver
from matplotlib import pyplot as plt
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from pykeyboard import PyKeyboard

class CNKI():
	def __init__(self,
		userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0",
	):																	 # 構造函數
		""" 類構造參數 """
		self.userAgent = userAgent
		""" 類常用參數 """
		self.workspace = os.getcwd()									 # 類工作目錄
		self.date = time.strftime("%Y%m%d")								 # 類構造時間

		self.mainURL = "http://www.cnki.net/"							 # 中國知網主頁
		self.searchURL = "http://kns.cnki.net/kns/brief/default_result.aspx"
		self.options = webdriver.FirefoxOptions()						 # 火狐驅動配置
		self.profile = webdriver.FirefoxProfile()
		self.session = Session()										 # 初始化一個可能需要用得到的session: CNKI反爬蟲仍然很強勢, requests包不太好辦
		self.headers = {"User-Agent": userAgent}						 # 設置請求頭
		self.downloadFolder = "download"								 # 下載文件夾
		self.tempFolder = "temp"										 # 存儲臨時文件的文件夾
		self.labelCompiler = re.compile(r"<[^>]+>",re.S)				 # 標籤正則編譯
		""" 類初始化 """
		self.session.headers = self.headers.copy()
		self.session.get(self.mainURL)									 # 定位中國知網主頁
		#self.profile.set_preference("dom.webnotifications.enabled",False)
		self.profile.set_preference("browser.download.folderList",2);
		self.profile.set_preference("browser.download.dir","{}\\{}".format(self.workspace,self.downloadFolder))
		#self.profile.set_preference("browser.helperApps.neverAsk.saveToDisk","pdf,caj");
		#self.profile.set_preference("browser.link.open_newwindow",3)
		if not os.path.exists("{}\\{}".format(self.workspace,self.downloadFolder)):
			string = "正在新建文件夾以存儲下載文件..."
			print(string)
			os.mkdir("{}\\{}".format(self.workspace,self.downloadFolder))# 新建文件夾存儲下載文件
	
		if not os.path.exists("{}\\{}".format(self.workspace,self.tempFolder)):
			string = "正在新建文件夾以存儲臨時文件..."
			print(string)
			os.mkdir("{}\\{}".format(self.workspace,self.tempFolder))	 # 新建文件夾存儲臨時文件		

	def search_for_details(self,url):
		html = self.session.get(url).text
		soup = BeautifulSoup(html,"lxml")
		div = soup.find("div",class_="wxBaseinfo")
		response = str()
		for p in div.find_all("p"):
			string = labelCompiler.sub("",str(p)).replace("\n","").replace("\t","")
			response += "{}\n".format(string)
			if string[:3] == "分類號": break
		return response
	
	def search(self,keyword,
		headless=False,
		page=1,
	):
		options = self.options.add_argument("--headless") if headless else self.options
		driver = webdriver.Firefox(self.profile)
		driver.implicitly_wait(10)										 # 設置等待超時
		driver.get(self.searchURL)
		driver.find_element_by_xpath("//input[@class='rekeyword']").send_keys(keyword)
		driver.find_element_by_xpath("//input[@class='researchbtn']").click()
		driver.switch_to_frame("iframeResult")
		if page==1:
			html = driver.find_element_by_xpath("//table[@width='100%' and @cellspacing='0' and @border='0' and @bgcolor='']").get_attribute("innerHTML")
			soup = BeautifulSoup(html,"lxml")
			aLabels = soup.find_all("a",class_="fz14")
			results = {
				"序號": [],
				"題名": [],
				"鏈接": [],
			}
			for i in range(len(aLabels)):
				results["序號"].append(i+1)
				title = self.labelCompiler.sub("",str(aLabels[i])).strip("\n").strip("\t").strip()
				results["題名"].append(title)
				href = aLabels[i].attrs["href"]								 # 這個鏈接是個僞鏈接, 如果打開它則會重定向回主頁, 但是裏面有一部分字段是有用的, 我找規律找出了這段有用的字段
				index1 = href.find("URLID")
				index2 = href.find("&",index1)
				trueURL = "http://kns.cnki.net/KCMS/detail/{}.html".format(href[index1+6:index2])
				results["鏈接"].append(trueURL)
			
			return results,driver
		else:
			driver.find_element_by_xpath("//a[text()='{}']".format(i)).click()
			html = driver.find_element_by_xpath("//table[@width='100%' and @cellspacing='0' and @border='0' and @bgcolor='']").get_attribute("innerHTML")
			soup = BeautifulSoup(html,"lxml")
			aLabels = soup.find_all("a",class_="fz14")
			results = {
				"序號": [],
				"題名": [],
				"鏈接": [],
			}
			for i in range(len(aLabels)):
				results["序號"].append(i+1)
				title = self.labelCompiler.sub("",str(aLabels[i])).strip("\n").strip("\t").strip()
				results["題名"].append(title)
				href = aLabels[i].attrs["href"]								 # 這個鏈接是個僞鏈接, 如果打開它則會重定向回主頁, 但是裏面有一部分字段是有用的, 我找規律找出了這段有用的字段
				index1 = href.find("URLID")
				index2 = href.find("&",index1)
				trueURL = "http://kns.cnki.net/KCMS/detail/{}.html".format(href[index1+6:index2])
				results["鏈接"].append(trueURL)
			driver.quit()
			return results

	def download(self,url,
		headless=False,
		default_download_path=r"C:\Users\lenovo\Downloads",
		download_path=r"F:\Temp",
	):
		k = PyKeyboard()
		options = self.options.add_argument("--headless") if headless else self.options
		driver = webdriver.Firefox(self.profile,options=options) if headless else webdriver.Firefox(self.profile)
		driver.get(url)
		try: driver.find_element_by_id("pdfDownF").click()
		except:
			return 0													 # return 0 是沒有pdf下載的選項
		count = 0
		while True:
			if count==0: time.sleep(5.)									 # 第一次等5秒
			else: time.sleep(1.)										 # 之後等1秒
			k.tap_key(13)												 # 敲擊回車鍵
			isFile = len(list(os.walk(default_download_path))[0][2])	 # 看看默認路徑下有幾個文件
			if isFile==0:												 # 沒下好, 繼續下
				count += 1
				continue
			elif isFile==1:
				rename = time.strftime("%Y%m%d%H%M%S")
				os.system(r"rename {}\*.pdf {}\{}.pdf".format(default_download_path,default_download_path,rename))
				os.system(r"move *.pdf {}".format(download_path))		 # 移動到下載路徑(不能有空格)
				driver.quit()
				return rename
			else:														 # 
				driver.quit()
				return 1												 # return 1 是系統邏輯錯誤

if __name__=="__main__":
	cnki = CNKI()
	#cnki.search("農業")
	cnki.download("http://kns.cnki.net/KCMS/detail/44.1267.S.20190529.1324.004.html")

分享學習,共同進步!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章