用到了requests,os,random,lxml,re,tkinter,threading,pyperclip,selenium,time等库的书栈爬虫

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()

猜你喜欢

转载自blog.csdn.net/whan1527/article/details/81265054