事燃cs释然 发表于 2020-9-10 19:14

一个可以把网上的小说保存的爬虫

本帖最后由 事燃cs释然 于 2020-9-13 17:08 编辑

应论坛中一些朋友的要求,已将代码打包成了exe程序,发布在另一个帖子中,不懂得python的同学可以直接绕路,附上另一个帖子链接:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1265020&page=1&extra=#pid34083564
初学者,写了个可以把网上的小说保存的爬虫,暂时就只能在笔趣阁用
附上代码,希望大佬前来指正{:1_893:}:
import requests
import re
import json
import os.path

header = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'
}
#定义全局变量,用于保存所有获取到的小说内容
story_all = []

#获取标题及章节链接地址
def main():
    url = input("请输入小说目录页地址(暂时仅支持‘http://www.xbiquge.la/’站内小说):")
    print("-----爬取开始-----")
    #获取目录页的HTML文本
    text = requests.get(url,header).content.decode('utf-8')
    #获取书名
    book_name = re.findall(r'<div id="info".*?h1>(.*?)<',text,re.DOTALL)
    #获取每个章节的章节名
    title = re.findall(r'<dd>.*?<a.*?>(.*?)</a>',text,re.DOTALL)
    #获取每个章节的链接地址
    loca = re.findall(r"<dd>.*?='(.*?)' >",text,re.DOTALL)
    all_info = have_stort(book_name, title, loca)
    if all_info:
      title = all_info['title']
      loca = all_info['loca']
    #因为title和loca的长度相同,所以以索引的方式遍历,方便取值
    for i in range(len(title)):
      #这里还是改一下吧,f""的形式,py3.6.2之后的版本才支持
      #所以就把它改成%s的占位符形式
      content(title,'http://www.xbiquge.la%s'%loca)
      #保存为json防止程序突然中断也方便为以后更新
      with open('%s.json'%book_name,'w',encoding='utf-8')as fp:
            json.dump(story_all,fp,ensure_ascii=False)
    #小说爬取完毕,开始保存
    print("@"*500)
    with open(r'%s.txt'%book_name, 'w',encoding='utf-8')as file:
      #遍历每一项,按顺序保存章节名和章节内容
      for story in story_all:
            file.write(story['title']+'\n')
            print(story['title'])
            file.write(story['story'])

#解析章节内容并保存
def content(title,url):
    #获取章节页的HTML文本
    text = requests.get(url,header).content.decode('UTF-8')
    #因为之前写过直接爬取所有内容的,爬取出来的文本都带有\r不好处理
    #所以就一句一句的获取了
    story_content = re.findall(r'    (.*?)<br',text,re.DOTALL)
    #因为有一个网友提出了一个bug:就是如果某个章节的内容只有一两句话,那么网站是不会进行首行缩进的,所以这里进行一次判断
    have_nav = re.findall(r'<div id="content">(.*?)<b',text,re.DOTALL)
    if not have_nav == []:
      have_nav = re.sub(r'\ufeff','',have_nav)
      have_nav = re.match(r"[ ]",have_nav)
      if not (have_nav):
            story_content = ["本章节内容无意义,可以跳过"]
    #因为章节名中有“”空格,没办法作为文件名,所以把空格去掉
    title = re.sub(' ','-',title)
    #有时候不知道为什么小说内容会爬取到一个空数组,所以这里添加了一个检测程序
    #如果爬取到的为空,就重新爬取,直到获取到为止
    if story_content==[]:
      content(title,url)
      return 0
    story=""
    #前面提到,因为我是一句一句爬取的,所以这里做一下拼接,顺便去空格
    for story_contents in story_content:
      story = story+story_contents.strip()+'\n'
    #将章节名称和章节内容保存为一个字典
    this_story = {
      "title":title,
      "story":story
    }
    print(this_story['title'])
    #将字典添加到开头定义的全局变量中
    story_all.append(this_story)

#判断该小说是否已经保存,如果已经保存则遍历章节过滤已保存章节
def have_stort(book_name,title,loca):
    global story_all
    #判断此小说是否保存过
    if os.path.isfile('%s.json'%book_name):
      #打开之前保存的json用以对照
      with open('%s.json'%book_name,'r',encoding='utf-8')as fp:
            storys_all=json.load(fp)
      have_index = 0
      #遍历,判断最后的一章
      if not storys_all == []:
            for index,story in enumerate(storys_all):
                for title_this in title:
                  title_this = re.sub(' ', '-', title_this)
                  if title_this == story['title']:
                        have_index = index+1
            #将已保存的章节的标题删除
            del title[:have_index]
            del loca[:have_index]
            story_all = storys_all
            print("本书已保存至'%s',将从此章之后开始保存"%story_all['title'])
      all_info = {
            'title':title,
            'loca':loca
      }
      #返回处理后的title和loca列表
      return all_info


if __name__ == "__main__":
    main()



最后说一下使用步骤:
1、前往http://www.xbiquge.la/,找到要保存的小说,复制那个小说的目录页链接

2、按要求输入链接地址和小说名
3、爬取开始
(因为是单线程运行,爬取速度略慢大概1-2秒一章)
4、爬取结束后,会将所有章节内容整合成一个txt文件

最后求一波热心值{:1_918:}

2020/9/11 15:10 第一次更新:当某章节只有一两行文本时,正则表达式无法匹配
2020/9/12 16:32 第二次更新:如果保存的是已经保存一次的小说,会从上一次保存的基础上保存,防止浪费时间
2020/9/13 17:02 第三次更新:每保存一章就会及时保存json,这样如果程序在运行期间被关闭或中断,下次爬取同一个小说是会自动识别已保存记录继续保存

事燃cs释然 发表于 2020-9-12 19:03

本帖最后由 事燃cs释然 于 2020-9-12 19:18 编辑

lxyadobe 发表于 2020-9-12 18:47
https://cowtransfer.com/s/370238e726d84f改了一下,我打包为exe 后,双击exe 就是一个黑框框闪一下
运行没什么问题啊,我帮你打包顺便运行了一下,我把打包的文件发给你https://cloud.189.cn/t/BNZVVjYbe2qy (访问码:7txi)

还有,你这个程序会出一个bug,就是,当一个ip多次访问新笔趣阁的话,新笔趣阁会返回503 Service Temporarily Unavailable的页面,你可以去爬取一个几百章的小说,然后在21行后面添加print(title)print(contents)
这两行代码,你就会发现它返回的是这样的
503 Service Temporarily Unavailable
[]

我的解决方法就写在我的代码的第60行,一直重复运行,直到爬取到数据为止
其实还有最好的方法是使用代{过}{滤}理ip,但是......呵呵,你懂得

事燃cs释然 发表于 2020-9-11 07:02

judgecx 发表于 2020-9-10 20:20
content(title,f,'http://www.xbiquge.la{loca}') 这段的f那里不加个逗号我运行不了
这样子的
首先‘content(title,f,'http://www.xbiquge.la{loca}') 这段的f那里不加个逗号我运行不了’
我的代码为content(title,f'http://www.xbiquge.la{loca}'),因为title和loca都是列表而且长度都相等,所以我用索引的方式进行了遍历,我注意到你的这行代码中都没有下标,应该就是这个问题
然后,你这个报错好像说第13行的input()有错误,但我实在找不到这个地方有什么可以错的:rggrg

jdg688 发表于 2020-9-10 19:35

代码怎么用啊

轮回丶 发表于 2020-9-10 19:47

谢谢分享

yxrsh 发表于 2020-9-10 19:57

这还是小白啊

卡尔文循环 发表于 2020-9-10 19:58

感谢分享!送上评分!也想问下代码要怎么用呢

旋风中的小聪明 发表于 2020-9-10 20:03

唔,支持一下,代码还是可以的,但看到评论有些不学py的朋友不会用,你可以打包exe

longbow2 发表于 2020-9-10 20:12

楼主这个对于我等爱在网上看小说又付不起费的人来说是一大福利啊,好好学习下,看能不能完善

judgecx 发表于 2020-9-10 20:18

运行不了.....

greatslime 发表于 2020-9-10 20:20

最近正好再看小说谢谢分享

judgecx 发表于 2020-9-10 20:20

content(title,f,'http://www.xbiquge.la{loca}') 这段的f那里不加个逗号我运行不了
这样子的
页: [1] 2 3 4
查看完整版本: 一个可以把网上的小说保存的爬虫