import requests #用来获取网页源码 import os #用来检查文件目录是否存在,以及创建目录 import random #python的随机库,用来获取随即内容 from lxml import html #导入lxml库中的html模块,用于解析html网页及使用.xpath定位元素。如果解析xml用etree import re #正则表达式的库 from tkinter import * #用来创建GUI界面 import threading #多线程模块,用于给下载列表创建单独线程 import pyperclip #读取字符串到剪贴板的库 from selenium import webdriver #网页自动化测试工具,用于控制浏览器 from selenium.webdriver.common.keys import Keys #自动化测试工具中用于发送键盘按键的模块 import time class 界面(Tk):#创建一个继承tkinter库TK模块的类 def __init__(self):#类的初始化函数 Tk.__init__(self)#调用Tk模块的初始化方法创建GUI界面 self.wm_title('html转markdown书籍下载')#使用Tk模块的wm_title方法修改窗口标题 self.开始下载页文本 = Label(self, text='请输入从第几页开始下载:')#使用Tk模块的Label方法创建文本显示 self.开始下载页文本.grid(row=0,column=0,sticky=W)#使用TK模块的.grid方法将组件放置在窗口的row=行,column=列,sticky=组件从哪个方向开始放置(N=上,E=右,S=下,W=左) self.开始下载页输入窗口=Entry(self)#使用Tk模块的Entry方法文本输入窗口 self.开始下载页输入窗口.grid(row=0,column=1,sticky=E)#使用TK模块的.grid方法将组件放置在窗口的row=行,column=列,sticky=组件从哪个方向开始放置(N=上,E=右,S=下,W=左) self.下载几本书文本=Label(self, text='请输入下载几本书:')#使用Tk模块的Label方法创建文本显示 self.下载几本书文本.grid(row=1,column=0,sticky=W)#使用TK模块的.grid方法将组件放置在窗口的row=行,column=列,sticky=组件从哪个方向开始放置(N=上,E=右,S=下,W=左) self.下载几本书输入窗口=Entry(self)#使用Tk模块的Entry方法文本输入窗口 self.下载几本书输入窗口.grid(row=1,column=1,sticky=E)#使用TK模块的.grid方法将组件放置在窗口的row=行,column=列,sticky=组件从哪个方向开始放置(N=上,E=右,S=下,W=左) self.界面文本提示=Label(self, text='界面文本提示')#使用Tk模块的Label方法创建文本显示 self.界面文本提示.grid(row=2,column=0,sticky=W)#使用TK模块的.grid方法将组件放置在窗口的row=行,column=列,sticky=组件从哪个方向开始放置(N=上,E=右,S=下,W=左) class 页面处理(threading.Thread): def __init__(self,界面,首页网址):#类的初始化函数 threading.Thread.__init__(self) self.界面=界面 self.首页网址=首页网址 self.继续=False self.开始下载按钮=Button(self.界面,text='开始下载',command=self.是否继续下载)#使用Tk模块的Button方法创建按钮,使用command方法将按钮和是否继续下载函数绑定。 self.开始下载按钮.grid(row=2,column=1,sticky=E)#使用TK模块的.grid方法将组件放置在窗口的row=行,column=列,sticky=组件从哪个方向开始放置(N=上,E=右,S=下,W=左) self.总页数=self.获取书籍总页数()#调用页面处理类的获取书籍总页数函数得到网站书籍总页数。 self.界面.界面文本提示["text"]='当前书籍总页数: '+self.总页数+'页'#替换界面文本提示组件的文本内容 driver= webdriver.Chrome()#创建浏览器自动测试工具对象 driver.get('d:\html转markdown\html转markdown.html')#打开html转markdown的网页 self.txt=driver.find_element_by_xpath("//textarea[@id='code']")#获取转换网页的文本输入框 self.转换=driver.find_element_by_xpath("//button[@value='tomarkdown']")#获取转换网页的转换按钮 def 下载(self): 开始下载页=int(self.界面.开始下载页输入窗口.get())#获取用户输入的开始下载页内容,并转换为整数类型 下载几本书=int(self.界面.下载几本书输入窗口.get())#获取用户输入的下载几本书内容,并转换为整数类型 总页数=int(self.总页数)#从网页中读取到的页数文本属于字符串类型,转换为整数型 if 开始下载页>0 and 下载几本书>0 and 开始下载页<总页数: #判断用户输入内容是否符合规则 for 下载第几页 in range(开始下载页,总页数+1): #将开始下载页和总页数转换为list列,并依次读取到变量 下载第几页 下载列表=self.首页网址+'?page='+str(下载第几页)+'&tab=latest' #组合新的网站地址 下载列表=self.网页源码(下载列表)#调用函数得到网页源码 下载列表=self.获取书籍列表(下载列表)#得到书籍界面相对地址 for 书籍地址 in 下载列表: 书籍地址=self.首页网址+书籍地址 #组合得到书籍界面绝对地址 书籍地址=self.网页源码(书籍地址)#获取书籍界面网页源码 源码 = html.fromstring(书籍地址) #使用html.fromstring解析网页,解析后才能使用.xpath定位 图片地址=源码.xpath('//div[@class="col-sm-3 col-xs-6"]/img/@src') #定位到图片位置读取到图片的相对地址,获取结果是一个list列表 书名=源码.xpath('//div[@class="col-xs-12"]/h1/text()') #定位到书名位置,获取书名文本,获取结果是一个list列表 书名=书名[0].rstrip().replace('?','?').replace('|','').replace(':',':').replace('/',':').replace('\\',':') self.界面.界面文本提示["text"]='下载第'+str(下载第几页)+'页的:'+书名 图片地址=图片地址[0] 文档概述=源码.xpath('//div[@style="text-indent: 2em;"]/text()')[0] if not os.path.exists('d:/书栈书籍/{}/'.format(书名)): #判断文件是否存在,如果不存在,表示这本书未下载 os.makedirs('d:/书栈书籍/{}'.format(书名)) #如果存储目录不存在则创建目录 with open('d:/书栈书籍/{}/fm.jpg'.format(书名), 'wb') as f: f.write(requests.get(图片地址, headers=self.获取headers()).content) with open('d:/书栈书籍/{}/文档概述.txt'.format(书名), 'a',encoding='utf-8') as f : f.write(文档概述) 书籍地址=self.获取书籍地址(书籍地址)#解析得到书籍内容页首页相对地址 书籍地址=self.首页网址+书籍地址[0]#得到书籍内容页绝对地址 源码=self.网页源码(书籍地址)#获取书籍内容页第一页源码 summary=re.split('<div class="catalog-list read-book-preview" id="sidebar">|<div class="tab-item manual-search">',源码)#分割得到HTML格式目录表 if len(summary)>2: summary=summary[1] self.txt.clear() pyperclip.copy(summary) self.txt.send_keys(Keys.CONTROL,'v') self.转换.click() summary=self.txt.get_attribute('value') os.mkdir('d:/书栈书籍/{}/{}'.format(书名,书名)) with open('d:/书栈书籍/{}/{}/summary.md'.format(书名,书名), 'a',encoding='utf-8') as f : f.write(summary) 源码 = html.fromstring(源码)#解析书籍内容页第一页 书籍主目录=源码.xpath('//div[@class="catalog-list read-book-preview"]//a/@href') #获取书籍主目录文章相对地址,获取结果是一个list列表 if len(书籍主目录): for 书页路径 in 书籍主目录: 书页地址=self.首页网址+书页路径 书页内容源码=self.网页源码(书页地址) 源码 = html.fromstring(书页内容源码)#解析书籍内容页第一页 书页内图片地址=源码.xpath('//div[@id="page-content"]//img/@src') 书页内容=re.split('<div class="article-body markdown-body editormd-preview-container" id="page-content">|<div class="row hung-read-link">',书页内容源码) if len(书页内容)>2: 书页内容=书页内容[1] 书页路径=书页路径.split('/') if not os.path.exists('d:/书栈书籍/{}/{}/{}/{}/img'.format(书名,书名,书页路径[1],书页路径[2])): #判断文件是否存在,如果不存在,表示这本书未下载 os.makedirs('d:/书栈书籍/{}/{}/{}/{}/img'.format(书名,书名,书页路径[1],书页路径[2])) #如果存储目录不存在则创建目录 for 书页内图片 in 书页内图片地址: if re.search('projects',书页内图片) != None: if re.match('http',书页内图片) is None : 图片地址=self.首页网址+书页内图片 图片名=书页内图片.split('/')[-1] 旧图片地址='<img src="{}"'.format(书页内图片) 新图片地址='<img src="/img/{}"'.format(图片名) 书页内容=书页内容.replace(旧图片地址,新图片地址) with open('d:/书栈书籍/{}/{}/{}/{}/img/{}'.format(书名,书名,书页路径[1],书页路径[2],图片名), 'wb') as f: f.write(requests.get(图片地址, headers=self.获取headers()).content) else: if re.match('http://static.bookstac',书页内图片) != None: 图片地址=书页内图片 图片名=书页内图片.split('/')[-1].replace('?raw=true','').replace('?','a') 旧图片地址='<img src="{}"'.format(书页内图片) 新图片地址='<img src="/img/{}"'.format(图片名) 书页内容=书页内容.replace(旧图片地址,新图片地址) with open('d:/书栈书籍/{}/{}/{}/{}/img/{}'.format(书名,书名,书页路径[1],书页路径[2],图片名), 'wb') as f: f.write(requests.get(图片地址, headers=self.获取headers()).content) self.txt.clear() pyperclip.copy(书页内容) self.txt.send_keys(Keys.CONTROL,'v') self.转换.click() time.sleep(0.5) 书页内容=self.txt.get_attribute('value') 书页内容 = re.sub('\*.*\[.*\]\(#.*\)', "",书页内容) with open('d:/书栈书籍/{}/{}/{}/{}/{}'.format(书名,书名,书页路径[1],书页路径[2],书页路径[3]), 'a',encoding='utf-8') as f : f.write(书页内容) else: print(书名+书页地址+'解析错误') else: print(书名+'解析错误') def 获取headers(self): #伪造requests爬虫头文件,让爬虫模拟浏览器 user_agent_list = [ \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", \ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", \ "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"] ua = random.choice(user_agent_list) #从列表中返回一个随机项 headers = {'User-Agent': ua, 'Referer': self.首页网址} #Referer 为访问时连接来源地址 return headers def 网页源码(self,网址): 源码 = requests.get(网址, headers=self.获取headers()) #requests.get和post方法都返回Response对象,里面存的是服务器返回的所有信息。包括响应头和响应状态码等。 源码=源码.text # Response对象的网页部分用.content读取字节码.text可以读取中文字符串。如果.text读取显示乱码,用.content.decode('utf-8')指定编码方式,中文常用GBK,GB2312等。 return 源码 #返回得到的结果 源码 def 获取书籍总页数(self): 主页源码 = html.fromstring(self.网页源码(self.首页网址)) #使用html.fromstring解析网页,解析后才能使用.xpath定位 书籍总页数=主页源码.xpath('//ul[@class="pagination"]/li[last()]/a/text()') #定位到页码位置读取文本,获取结果是一个list列表 书籍总页数=书籍总页数[0].split('.') #用小数点分割,分割后返回一个list列表 书籍总页数=书籍总页数[2] #读取列表中的第3项,得到总页数 return 书籍总页数 #返回得到的结果 def 获取书籍列表(self,下载列表页源码): 源码 = html.fromstring(下载列表页源码) #使用html.fromstring解析网页,解析后才能使用.xpath定位 书籍列表=源码.xpath('//a[@class="clearfix tooltips"]/@href') #定位到页码位置读取,获取结果是一个list列表 return 书籍列表 def 是否继续下载(self): if self.继续==False: self.开始下载按钮["text"]='停止下载' self.继续=True if threading.activeCount()<2: #用threading.activeCount()获取当前开启线程数,如当前开启线程数小于2表示多线程没有开启。 t = threading.Thread(target=self.下载,name='xiazai') t.start() #调用threading.Thread的.start方法开启线程 else: self.开始下载按钮["text"]='开始下载' self.继续=False def 获取书籍地址(self,书籍地址源码): 源码 = html.fromstring(书籍地址源码) #使用html.fromstring解析网页,解析后才能使用.xpath定位 书籍地址=源码.xpath('//a[@title="马上阅读"]/@href') #定位到页码位置读取,获取结果是一个list列表 return 书籍地址 界面=界面() 页面处理=页面处理(界面,'https://www.bookstack.cn/') 界面.mainloop()
用到了requests,os,random,lxml,re,tkinter,threading,pyperclip,selenium,time等库的书栈爬虫
猜你喜欢
转载自blog.csdn.net/whan1527/article/details/81265054
今日推荐
周排行