Python Scrapy 爬虫入门:爬取网络小说实战
前言
网络小说资源丰富,有时需要本地保存以便离线阅读。本教程以初级案例为例,演示如何使用 Python 的 Scrapy 框架抓取小说内容。
声明:请尊重作者劳动成果,支持正版小说。
新建 Scrapy 爬虫项目
Scrapy 是 Python 的爬虫框架。使用以下语句安装 Scrapy:
pip install scrapy
安装完成后,打开命令行窗口,转到目标目录下,使用下面命令新建 Scrapy 项目:
scrapy startproject ebook
新建后会出现基础代码框架。进入项目目录并创建爬虫:
cd ebook
scrapy genspider example example.com
使用 genspider 根据模板创建一个爬虫,在项目的 spider 目录下会多一个 example.py 文件。
实例一:起点中文网
第一个例子选取起点中文网的一本小说。
(此处展示网页结构截图)
使用命令创建爬虫文件:
scrapy genspider qxzz qidian.com
将 start_urls 中的内容改为这本小说的地址。打开浏览器开发者工具查看页面结构。
爬取章节地址
在右侧窗口中找到目录所在的标签。每一章节的内容链接位于列表项中。
# -*- coding: utf-8 -*-
import scrapy
class QxzzSpider(scrapy.Spider):
name = 'qxzz'
allowed_domains = ['qidian.com']
start_urls = ['https://book.qidian.com/info/1011146676/']
def parse(self, response):
# 获取目录列表
pages = response.xpath('//div[@id="j-catalogWrap"]//ul[@class="cf"]/li')
for page in pages:
url = page.xpath('./child::a/@href').get()
print(url)
程序编写完之后,在命令行运行下面语句查看结果:
scrapy crawl qxzz
如果遇到 "No module named win32api" 的错误,可执行 pip install pypiwin32。
爬取每章内容
从页面结构可以看出,整个章节在 main-text-wrap 标签中,章节名和正文分别位于不同的子标签内。修改后的代码如下:
# -*- coding: utf-8 -*-
import scrapy
class QxzzSpider(scrapy.Spider):
name = 'qxzz'
allowed_domains = ['qidian.com']
start_urls = ['https://book.qidian.com/info/1011146676/']
def parse(self, response):
pages = response.xpath('//div[@id="j-catalogWrap"]//ul[@class="cf"]/li')
for page in pages:
url = page.xpath('./child::a/@href').get()
req = response.follow(url, callback=self.parse_chapter)
yield req
def parse_chapter(self, response):
title = response.xpath('//div[@class="main-text-wrap"]//h3[@class="j_chapterName"]/text()').get().strip()
content = response.xpath('//div[@class="main-text-wrap"]//div[@class="read-content j_readContent"]').get().strip()
内容的保存
在爬取内容后,可以以 HTML 形式保存。为了排序方便,给文件名加序号。起点网在 li 标签内有 data-rid 属性可作为序号。
# -*- coding: utf-8 -*-
import scrapy
class QxzzSpider(scrapy.Spider):
name = 'qxzz'
allowed_domains = ['qidian.com']
start_urls = ['https://book.qidian.com/info/1011146676/']
def parse(self, response):
pages = response.xpath('//div[@id="j-catalogWrap"]//ul[@class="cf"]/li')
for page in pages:
url = page.xpath('./child::a/@href').get()
idx = page.xpath('./@data-rid').get()
req = response.follow(url, callback=self.parse_chapter)
req.meta['idx'] = idx
yield req
def parse_chapter(self, response):
idx = response.meta['idx']
title = response.xpath('//div[@class="main-text-wrap"]//h3[@class="j_chapterName"]/text()').get().strip()
content = response.xpath('//div[@class="main-text-wrap"]//div[@class="read-content j_readContent"]').get().strip()
filename = './down/%s_%s.html' % (idx, title)
cnt = '<h1>%s</h1> %s' % (title, content)
with open(filename, 'wb') as f:
f.write(cnt.encode('utf-8'))
注意:需要在目录下新建 down 文件夹,否则可能会出错。
同一个网站,电子书网页的源代码架构通常一致,只需改变网址即可爬取该网站的其他电子书。爬取结束并保存后,可以通过 Sigil 等工具制作 epub 电子书或转换格式。
实例二:其他小说网站
第二个例子选择另一本小说,逻辑类似但选择器不同。
(此处展示网页结构截图)
目录和正文内容所在的标签如下:
(此处展示网页结构截图)
# -*- coding: utf-8 -*-
import scrapy
class DmhsSpider(scrapy.Spider):
name = 'dmhs'
allowed_domains = ['m.x23us.com']
start_urls = ['https://m.x23us.com/html/51/51940/']
def parse(self, response):
pages = response.xpath('//div[@class="cover"]//ul[@class="chapter"]/li')
for page in pages:
url = page.xpath('./child::a/@href').get()
idx = str(url[0:8])
req = response.follow(url, callback=self.parse_chapter)
req.meta['idx'] = idx
yield req
def parse_chapter(self, response):
idx = response.meta['idx']
title = response.xpath('//div[@class="content"]//h1[@id="nr_title"]/text()').get().strip()
content = response.xpath('//div[@class="content"]//div[@class="txt"]').get().strip()
filename = './down/%s_%s.html' % (idx, title)
cnt = '<h1>%s</h1> %s' % (title, content)
with open(filename, 'wb') as f:
f.write(cnt.encode('utf-8'))
在该网站中没有 data-rid 属性,但发现章节网页地址的数字随章节号递增,因此截取地址中的数字来排序保存后的 HTML 文件。
结语
通过上述两个实例,掌握了使用 Scrapy 进行基础网页数据抓取的方法。实际应用中需注意网站的 robots 协议及相关法律法规,合理合法地使用爬虫技术。


