用Python抓小说
前言
有一段时间没有看小说了,前两天看到了唐家三少写的《斗罗大陆II绝世唐门》,突然燃起了看的欲望,就在微信读书上看了起来。
免费章节不多,没多久就看完了。还想继续看,于是,开始花钱了。最终花了¥200+之后,看完了整本书,这时,我看到了第三部...
心想,这也太费钱了。于是想从网上找个资源看看,可看来看去,适合手机端观看的网站且无遮挡广告的太少了,于是想到了用程序抓小说的方式。
功能模块
本次使用的是Python 2.7.6
。
选择了用Python
来抓取小说,毕竟一直听说Python
的库非常丰富,自己也没有好好的学过Python
,想通过实战来提升一下自己。
大致确定了一下功能,如下:
-
1、根据url抓取网页内容
-
2、分析,获取节点
-
3、保存小说列表
-
4、根据小说列表,一个个抓取小说内容
-
5、将结果写入文件
至此,这样小说就可以抓取完毕了。
选择目标
在目标选择方面,建议选择质量高一点。笔者在这方面没有多做纠结,就找了一个一下子搜到的网站:www.xs.la。
列表页分析
目标很简单,只需要获取小说的章节和对应的链接就好,目标网站的列表页结构比较简单,可以很简单就分析好。
红色框:这里有id="list"
,我们可以用ID
的唯一来找到列表内容。
绿色框:有链接,有章节,样式为空
蓝色框:可以不关注的内容,但是样式不为空
小说内容页分析
小说内容也就有些麻烦了,虽然相对于列表页而言,结构简单很多。
同样可以根据ID
的唯一找到内容块。
但是,内容里有不少东西是需要去掉的,比如
、<br/>
、等,当然还有些内容可以替换。当然,还有不少内容可以替换,那个时候,对于内容的分析就更加严谨了。
抓取网页很简单--urllib
PHP
中,有几个方法可以获取网页内容,比如file_get_contents()
,fopen()
,curl
等。而Python
中我就不知道了,在一番搜索之后,终于让我找到了解决方案。
直接上代码:
# -*- coding: utf-8 -*-
import urllib
url = 'http://www.xs.la/1_1537'
html = urllib.urlopen(url).read()
print(html)
这样,直接将网页的内容抓取了下来。
不过有时候会出现抓取失败的情况,这个时候,需要我们让它有更好的兼容性,不然后续操作会出错的。
# -*- coding: utf-8 -*-
import urllib
url = 'http://www.xs.la/1_1537'
html = urllib.urlopen(url).read()
if not html:
print('未抓取到网页')
exit(0)
print(html)
网页抓取完成,那么怎么分析它呢?
正则?貌似会有点麻烦呢,如果能够像jQuery
那样,操作html
的dom
就好了。
项目地址:github
附上urllib的文档:urllib
强大的BeautifulSoup
如果找不到这个库的话,我真的会默默的写正则去,幸好让我遇到了它。
献上BeautifulSoup
的文档:BeautifulSoup
献上代码:
# -*- coding: utf-8 -*-
import urllib
from bs4 import BeautifulSoup
...
dom = BeautifulSoup(html)
if not dom:
print('未获取到dom')
exit(0)
print(dom)
直接获取到了html
的dom
,同时根据文档,我们可以使用select
方法获取到小说章节列表。
# -*- coding: utf-8 -*-
import urllib
from bs4 import BeautifulSoup
...
dom = BeautifulSoup(html)
if not dom:
print('未获取到dom')
exit(0)
storyTagList = dom.select('div#list dd')
print(storyTagList)
继续,我们只是获取到了N个标签,还没有获取到里面的想要的内容。
献上完整代码,同时,添加了备注:
# -*- coding: utf-8 -*-
import urllib
from bs4 import BeautifulSoup
''' 获取网页 '''
url = 'http://www.xs.la/1_1537'
html = urllib.urlopen(url).read()
if not html:
print('未抓取到网页')
exit(0)
''' 获取dom '''
dom = BeautifulSoup(html)
if not dom:
print('未获取到dom')
exit(0)
''' 获取小说列表dom '''
storyTagList = dom.select('div#list dd')
if not len(storyTagList):
print('列表为空')
exit(0)
''' 获取小说列表 '''
storyList = []
for tag in storyTagList:
''' 获取a标签 '''
aTag = tag.select('a')
if not len(aTag):
continue
''' 判断style是否为空,若不为空,则跳过 '''
if aTag[0].get('style'):
continue
storyInfo = {}
storyInfo['name'] = aTag[0].string.encode("utf8")
storyInfo['href'] = aTag[0]["href"]
storyList.append(storyInfo)
if not len(storyList):
print('格式化列表失败')
exit(0)
print(storyList)
项目地址:github
内容分析、获取
遍历很简单,直接上代码了(这里在抓取到第一篇之后,直接退出了):
# -*- coding: utf-8 -*-
import re
import urllib
from bs4 import BeautifulSoup
...
'''遍历列表,获取章节内容'''
compeleteList = {}
for index, chapterInfo in enumerate(storyList):
try:
articleUrl = "%s%s" % ('http://www.xs.la', chapterInfo['href'])
print('获取章节:%s' % chapterInfo['name'])
html = urllib.urlopen(articleUrl).read()
if not html:
raise Exception('未获取到%s的内容' % (chapterInfo['name']))
print('获取章节dom')
dom = BeautifulSoup(html)
if not dom:
raise Exception('未获取到dom')
pass
'''获取章节内容dom'''
print('获取章节内容dom')
contentTags = dom.select('div#content')
if not len(contentTags):
raise Exception('内容为空')
pass
'''解析章节内容'''
print('解析章节内容')
contentTagsStr = str(contentTags[0])
contentFormat = re.sub(r'<br.*?/?>|</?div.*>|<script.*t>|\&.*?;| | ', "\n", contentTagsStr)
contentList = contentFormat.split("\n")
contentStr = ''
'''拼接章节内容'''
print('拼接章节内容')
for paragraph in contentList:
paragraph = str(paragraph)
paragraph = paragraph.strip()
if len(paragraph) == 0:
continue
pass
contentStr += '<p>';
contentStr += str(paragraph);
contentStr += '</p>';
contentStr = contentStr.replace('"', "'")
contentStr = contentStr.replace("'", "'")
print(contentStr)
exit(0)
except Exception as e:
print(e)
其实这里我做了不少转化,将其拼接成了<p>*</p>
格式的,这样我们放在任何一个地方,也可以更好的去控制其展示的样子。
在抓取过程中,我遇到了两个问题。
问题一:抓取网页超时的问题,这个困扰了我一段时间,最终通过度娘解决了:
import socket
socket.setdefaulttimeout(60)
或者转战urllib2
:
import urllib2
urllib2.urlopen(url, data=None, timeout=60)
问题二:抓取的小说里,有时候有些章节是重复了,怎么办?直接建了一个完成的字典。
...
compeleteList = {}
for index, chapterInfo in enumerate(storyList):
chapterName = chapterInfo['name']
if compeleteList.has_key(chapterName):
continue
pass
else:
compeleteList[chapterName] = chapterName;
pass
...
最后,我们将内容直接写入文件,小说就抓取完毕。
项目地址:github
前台展示地址:读书
愿景
到这里,小说抓取完毕,在代码层,没有任何设计感而言,后期准备将其功能模块化。
同时,加入重试机制、过滤广告、建立任务系统,通过配置,可以直接抓取指定网站的小说,并时时在前台展示。
结语
在编写脚本的过程中有好几次想要放弃,主要还是对于Python
不熟,如果是用PHP
写,我可能会写的更快更好吧。
花了一天多的时间,从对于Python
的不了解,到写出一个还算能用的小功能,内心的成就感还是蛮不错的。
-- EOF --
本文转载自IMJCW
原文链接:用Python抓小说