1、背景
看了下记录,已经有一个月没有新增爬虫站点了,懈怠了懈怠了/抱头。
这次要整的网站是http://www.cartoonmad.com,漫画数目不多,在一万以下,属于比较简单的网站。貌似是使用ASP(Active Server Pages 动态服务器页面)写的,详情可以查看[1],是许多年前语言了,但问题不大,只要能看到就能爬。网站也是老牌网站(据说),有些是目前vomic漫画上没有的,资源质量也还不错。只是页面加载慢和网页结构比较不规范,但这并不影响爬虫的编写。
2、步骤
爬虫主要使用python的scrapy和requests库,由于是第八个爬取的漫画站点了,早已有了一套爬取流程。主要流程为:找最大id、详情页、章节信息、图片信息。
0、首先查看robots协议(没有,过)
如果有的话,还是建议爬取别人站点时遵循robots协议的内容,合理设置请求频率和爬取页面,做一个友好的爬虫~~(如果有sitemap等地址的话,也有利于分析页面)~~
1、寻找最大id
为了减轻爬虫逻辑,最好的办法就是找规律,比如找到爬取页的最大id、列表页最大页数、接口参数的最大值。漫画详情页地址为:https://www.cartoonmad.com/comic/5292.html,5292
就是该漫画的id了,那只需找到最大id即可。一般站点会有最新上架的漫画页面,这个站点也不例外:https://www.cartoonmad.com/comic99.html,第一个就是最新的漫画,max_id为8783
。
编写获取max_id的代码如下:
# 获取最新的漫画id
def get_max_id(self):
url = 'https://www.cartoonmad.com/comic99.html'
r = requests.get(url)
r.encoding = r.apparent_encoding
temp = etree.HTML(r.text).xpath('//a[@class="a1"]/@href')[0]
max_id = re.findall(r'\d+', temp)[0]
return int(max_id)
并编写start_requests
函数:
def start_requests(self):
max_id = self.get_max_id()
while max_id >= 0:
comic_id = max_id
url = f'https://www.cartoonmad.com/comic/{max_id}.html'
print(f'正在爬取[{url}]')
yield scrapy.Request(url, callback=self.parse_main, meta={'comic_id': str(comic_id)})
max_id -= 1
2、详情页信息
漫画主要采集的包括:标题,简介,关键,分类,作者名称等
页面结构清晰,但是dom树比较糟糕,使用xpath提取时用了不少的contains
(只贴出xpath,其他处理参考完整代码):
-
标题:
//*[contains(text(), "簡介")]/text()
-
简介:
//*[contains(text(), "簡介")]/..//td//text()
-
关键字:
//*[contains(@href, "/tag.asp")]/text()
-
分类:
//*[contains(text(), "漫畫分類")]/..//a/text()
-
作者名称:
//*[contains(text(), "原創作者:")]/text()
-
星级:
//span[contains(@class, "vstar")]/@class
-
封面图:
//*[@class="cover"]/../img/@src
全部信息如下:
3、章节信息
主要采集章节名称,图片数目,章节下标,是否更新主表(只有在最新一章节的时候采需要更新到主表里去)等,这部分也同样是xpath,具体不再赘述,详情参考完整代码。不过部分页面上显示的章节信息并不完整。
4、图片信息
这个站点的图片信息比较简单,没有接口也没有什么反爬。不过图片是通过重定向获取到真实地址的,且一页只有一张图片,且手机端也是,还有广告(体验极差),但还是能通过获取最大页码去拼接图片地址的方式得到所有的真实页图片地址。这样从详情页进来爬取完一个章节的所有图片,只需要请求三次即可:详情业+章节页 + 重定向。
再次建议编写爬虫时,尽量减少请求的次数,这样可以降低被爬取方的服务器压力和提升爬取方的爬取效率
先获取第一张图的src和总页数:
first_src = self.vcrawl('//img[contains(@src, "?file=")]/@src', response)
page_all = response.xpath('//select/option/text()').extract()
if first_src and page_all:
page_all = re.findall(r'\d+', page_all[-1])
if page_all:
meta['page_all'] = int(page_all[0])
# print(first_src)
yield scrapy.Request(first_src, callback=self.parse_page, meta=meta)
构造请求,并在parse_page
中获取到重定向之后的real_src,并拼接出所有图片地址:
page_all = response.meta['page_all']
real_src = response.url
real_src_head = real_src.rsplit('/', 1)[0]
real_src_end = real_src.rsplit('/', 1)[-1].split('.')[-1]
for i in range(1, page_all+1):
sort_index = i
page_url = f'{real_src_head}/{i:03}.{real_src_end}'
部分结果如下:
到此爬取结束。
3、总结
像此类小众网站一般不会有反爬措施,而且数据量小,基本能找到构造url或者图片地址的办法。但还是要注意爬取频率,我一般都会在各站点的爬虫中指定爬取频率,类似:
custom_settings = {
'RANDOMIZE_DOWNLOAD_DELAY': True,
'DOWNLOAD_DELAY': 1,
}
且行且珍惜,下一站爬取动漫屋:http://www.dm5.com/manhua-list-pay0/
参考资料: