【萌新向】在线小说正本抓取+内容简单排版【塔读文学】
这次主要跟大家分享一下python爬虫使用过程中可能会遇到的一些小问题和爬取的简单思路。我本身也不是程序员出身,python相关内容也都是自学,如果各位大佬发现我的代码里有可以优化、改进的地方,还请不吝赐教。即便是跟我一样的普通萌新,如果发现了更好的解决方法也请分享出来,大家一起进步。
任务目标:
从塔读文学的网站里抓取小说,并且保存为TXT格式。
简单级应用,输入小说目录网址,抓取全部的内容。
程序流程:
1、抓取小说目录网页信息
2、获取全部章节的名称和链接
3、依次进入每一章的页面,抓取内容并按照顺序写入同一个txt文件里
对萌新来说使用到的技术:
1、urllib.request库-----操作url
2、循环与break、continue
3、time库 -------系统暂停
4、lxml库 --------获取html代码里的指定内容
5、string.replace()方法 --------修改替换字符串内容
6、with open() 方法操作文件
都是一些简单的技术,思路也比较简单,给大家做一个参考。
首先是章节内容,通过DevTools可以发现文章内容在一个div里,整个html代码有相关注释
那么复制xpath之后,任务就变成了获取 【 //*[@id="partContent"] 】下面的所有P标签的内容形成一整个章节内容。
我们先从简单的开始做,获取网页内容,然后找到所有章节文字打印出来看看效果。
# -*- coding: utf-8 -*-
"""
Created on Wed Apr7 15:32:39 2021
@author: roshinntou
"""
from lxml import etree
import urllib.request
import time
mainUrl = 'http://www.tadu.com'
def get_html_code(url):
max_count = 6
htmlcode = ''
for i in range(max_count):
print('No.'+str(i)+' start load:' + url)
try:
req_one = urllib.request.Request(url)
req_one.add_header('User-Agent', 'Mozilla/6.0')
res_one = urllib.request.urlopen(req_one, timeout=60)
htmlcode = res_one.read().decode('utf-8')
res_one.close()
break
except:
if i < max_count:
continue
else:
print('多次重复读取网页信息失败')
time.sleep(1)
return htmlcode
if __name__ == '__main__':
code = get_html_code('http://www.tadu.com/book/725327/86240835/')
html = etree.HTML(code)
text = html.xpath('//*[@id="partContent"]/p')
for i in text:
print(i.text)
这里urllib.request获取html代码的过程单提取出了一个方法,我把核心代码放进for循环中,并加入 try/catch模块,如果一切正常就break结束当前循环,如果报错就判断重复了多少次,在次数规定内的话,使用continue重复执行核心代码,如果超出规定的次数则提示错误。(友情提示:爬取网页代码经常会因为网络波动或者其他原因超时报错。不加入循环方法的话要么爬虫程序停止,要么这一部分的内容没有爬取到,会影响最后的结果。)
然后是内容的处理,通过xpath 我们获取到了 partContent div下的所有P标签,然后通过for循环打印每一个p标签的text文本。
但是执行结果是:
读取html代码的模块并没有报错重复执行,只执行了一次就结束了。整个结果没有报错,证明至少所有步骤都执行下来了包括for循环也没有出错,没有超出下标之类的错误的同时有没有任何结果,应该时for循环执行了0次,P标签没有获取到。
那么我们直接打印获取出来的html代码看看是什么情况
结果是:
可以看到,DIV都找到了,但是里边(红框位置)没有文章内容。所以刚刚执行没有报错,也没有结果。
那么这里应该是之后动态加载的。
重新刷新页面,看看network内部的内容
然后发现这个script里有我们需要的东西
乱码是需要utf-8格式重新编码,callback 是js的回调,那么网页代码里肯定有相关链接。我们打开网页源代码搜索一下“1462b”这个开头的东西可以看到:
可以看到这个input 的 ID就叫 bookPartResourceUrl ,value里的内容就是刚刚的链接了。那么我们现在获取一下文章页面里这个链接的url地址,然后再次获取一下这个url地址的结果看看。主方法的代码是:
if __name__ == '__main__':
code = get_html_code('http://www.tadu.com/book/725327/86240835/')
html = etree.HTML(code)
input_info = html.xpath('//*[@id="bookPartResourceUrl"]').get('value')
code = get_html_code(input_info)
print(code)
第一步获取这个页面的信息,然后找到这个input元素,获取它的value属性,获得的是一个url链接,然后再次读取这个链接的信息,得到的结果是:
可以看到读取了2次网页之后,获取到了文章内容。每行内容都在P标签内。
这个时候就可以使用了,使用方法有多种:
1、可以直接把包含P标签的HTML代码保存起来,然后放在网站内使用。
2、存txt的情况下,可以用 split 方法以'</p><p>'为目标把这段内容截取成数组,代码如下:
string.split('</p><p>')
3、我是直接使用replace方法,把'</p><p>'替换成‘\n’然后前后内容固定的,直接截取一下string就获得了整张内容,然后就可以直接写入txt文件了。不用循环录入每一句的话,性能消耗也比较少
写入的方法也很简单:
with open('网游:每十小时创造一个BUG.txt','a',encoding='utf-8') as file:
file.write('\n'+title+'\n')
file.write(text+'\n')
print('保存成功!')
用with open 的 a 模式,文件名可以获取一下当作参数传进来,然后标题和内容换好行,连续写入同一个文件就行,现在主流的手机小说软件都是一个txt,多个之间也不好切换导入。别把每一章单独存一个文件。
到现在为止,我们已经能根据章节URL获取到章节的文字内容了,下一步,我们需要一次获取所有章节的标题和URL,然后依次读取所有章节内容,保存到一个txt文件里
先看一下目录结构:
也是很简单的结构,每一个章节对应一个a标签。xpath 是//*[@id="content"]/div/div/a
所以我们获取章节并进一步获取文章内容的方法应该是:
def get_all_url(url):
code = get_html_code(url)
print('get etree')
html = etree.HTML(code)
urlList = html.xpath('//*[@id="content"]/div/div/a')
for i in urlList:
title = i.text
title = title.replace(" ","").replace("\r\n","").replace(" ","").replace("\r","").replace("\n","")
url = i.get('href').replace(" ","").replace("\r\n","").replace(" ","").replace("\r","").replace("\n","")
print('title:'+title)
print('url:'+mainUrl+url)
get_text_by_url(mainUrl+url,title)
因为章节名称前后有空格和换行,我就给他们全部替换了一下。获取纯文本信息。
for循环是将每一章的标题和URL传给刚刚我们做好的方法里,保存内容用的。
最后:
完整的代码是:
# -*- coding: utf-8 -*-
"""
Created on Thu Apr8 11:48:39 2021
@author: roshinntou
"""
from lxml import etree
import urllib.request
import time
mainUrl = 'http://www.tadu.com'
def get_html_code(url):
max_count = 6
htmlcode = ''
for i in range(max_count):
print('No.'+str(i)+' start load:' + url)
try:
req_one = urllib.request.Request(url)
req_one.add_header('User-Agent', 'Mozilla/6.0')
res_one = urllib.request.urlopen(req_one, timeout=60)
htmlcode = res_one.read().decode('utf-8')
res_one.close()
break
except:
if i < max_count:
continue
else:
print('多次重复读取网页信息失败')
time.sleep(1)
return htmlcode
def get_all_url(url):
code = get_html_code(url)
print('get etree')
html = etree.HTML(code)
urlList = html.xpath('//*[@id="content"]/div/div/a')
for i in urlList:
title = i.text
title = title.replace(" ","").replace("\r\n","").replace(" ","").replace("\r","").replace("\n","")
url = i.get('href').replace(" ","").replace("\r\n","").replace(" ","").replace("\r","").replace("\n","")
print('title:'+title)
print('url:'+mainUrl+url)
get_text_by_url(mainUrl+url,title)
def get_text_by_url(url,title):
code = get_html_code(url)
html = etree.HTML(code)
input_info = html.xpath('//*[@id="bookPartResourceUrl"]').get('value')
print(input_info)
code = get_html_code(input_info)
#text = code.split('</p><p>')
text = code.replace('</p><p>', '\n')
with open('网游:每十小时创造一个BUG.txt','a',encoding='utf-8') as file:
file.write('\n'+title+'\n')
file.write(text+'\n')
print('保存成功!')
if __name__ == '__main__':
get_all_url('http://www.tadu.com/book/catalogue/743142')
#get_text_by_url('http://www.tadu.com/book/749916/91083959/','title')
每次执行的时候复制小说的目录URL到get_all_url 方法的参数里,然后把上边with open里的文件名改一下就行。
到现在为止,我们就成功的通过目录URL获取到小说的全部信息了。
塔读这边抓的太快会被封。请注意。
程序还有很多可以改进的地方:
1、输入书名,自动搜索下载的功能,也比较简单,搜索出来的小说链接是总页链接
比如:http://www.tadu.com/book/725327 这个是总页链接,但是目录链接是固定的,book 和后边id编号之间添加catalogue,既就是http://www.tadu.com/book/catalogue/725327,获取链接之后可以自己拼接一下。
还有就是这个网站的搜索,url里隐藏了参数。直接搜索显示默认url:http://www.tadu.com/search
实际上还是有提交数据的
然后测试一下接口:
发现参数是http://www.tadu.com/search?query=参数
这样就可以直接查询并获取内容了。
2、多进程爬取。现在的代码下载一个一两千章的小说要挺久的。可以多进程一起爬,简单点的方法是全部独立下载然后重新拼接在一起。或者使用协程处理。
不过我这边没有http代{过}{滤}理,老是被封比较无奈,等我搞到了稳定能用的http代{过}{滤}理之后再来跟大家分享多进程协程处理爬虫的开发心得。
附上完整代码:
gentlespider 发表于 2021-4-8 13:23
楼主多进程/多线程/携程需要考虑一个问题就是顺序的问题。可以带一个flag过去,到时候合并也能按f ...
之前研究多线程,感觉效率没有想象中那么好,查了一下,好像是因为即使多线程,在一个进程内也只有一个gil,gil导致性能下降。
后来用多进程之后效率一下子起飞,只是多个进程之间不互通数据,只能考虑队列或者管道,队列的文档多一些,好像更容易实现的样子。不过我还没彻底掌握,还在不断探索之中。。。 gentlespider 发表于 2021-4-8 16:45
获取的顺序一定会乱啊,多进程多线程携程都一样。为啥会快,因为是异步,异步获取的顺序就是不确定的,所 ...
单独把文件合并,还是加顺序标记存进内存最后整合?
我之前是加锁的方式来依次生成的,比如3条线程,a线程在a锁 解锁 后执行程序,执行完之后释放b锁,然后b线程再执行,释放C锁,以此类推。保证写入顺序的。看来我还有很多需要学习的 {:1_921:}楼主多进程/多线程/携程需要考虑一个问题就是顺序的问题。可以带一个flag过去,到时候合并也能按flag的顺序合并 二次爬取的思路蛮好的,学习了 大神啊,厉害了 收藏备用学习哈 够详细,学习了~感谢分享~~ roshinntou 发表于 2021-4-8 15:50
之前研究多线程,感觉效率没有想象中那么好,查了一下,好像是因为即使多线程,在一个进程内也只有一个gi ...
多线程在IO密集型操作程序上还是蛮快的,大多数爬虫程序都是IO密集型,没有用到CPU的,所以相对来说大多时候的爬虫程序多线程要比多进程快,但也要看实际程序需求和你计算机的硬件咋样。 gentlespider 发表于 2021-4-8 15:57
多线程在IO密集型操作程序上还是蛮快的,大多数爬虫程序都是IO密集型,没有用到CPU的,所以相对来说大多 ...
嗯嗯,那我再研究研究。操作IO的话只要顺序别乱应该好写很多吧。 roshinntou 发表于 2021-4-8 16:41
嗯嗯,那我再研究研究。操作IO的话只要顺序别乱应该好写很多吧。
获取的顺序一定会乱啊,多进程多线程携程都一样。为啥会快,因为是异步,异步获取的顺序就是不确定的,所以开始我说加个flag,方便爬完按顺序合并
页:
[1]
2