目录:Python网络爬虫实战系列
- Python网络爬虫实战之一:网络爬虫理论基础
- Python网络爬虫实战之二:环境部署、基础语法、文件操作
- Python网络爬虫实战之三:基本工具库urllib和requests
- Python网络爬虫实战之四:BeautifulSoup
- Python网络爬虫实战之五:正则表达式
- Python网络爬虫实战之六:静态网页爬取案例实战
- Python网络爬虫实战之七:动态网页爬取案例实战 Selenium + PhantomJS
- Python网络爬虫实战之八:动态网页爬取案例实战 Selenium + Headless Chrome
- Python网络爬虫实战之九:Selenium进阶操作与爬取京东商品评论
- Python网络爬虫实战之十:利用API进行数据采集
- Python网络爬虫实战之十一:Scrapy爬虫框架入门介绍
- Python网络爬虫实战之十二:Scrapy爬虫三个实战小案例
- Python网络爬虫实战之十三:Scrapy爬取名侦探柯南漫画集
- Python网络爬虫实战之十四:Scrapy结合scrapy-splash爬取动态网页数据
正文:
本文爬取的是这个网站:hhttp://comic.kukudm.com/comiclist/5/
一、创建项目
在开始爬取之前,我们必须创建一个新的Scrapy项目。 进入打算存储代码的目录中,运行下列命令:
scrapy startproject conan
生成的工程结构如图:
conan1.jpg
二、shell分析
使用 Scrapy 内置的 Scrapy Shell 分析一下目标网页
1、分析《名侦探柯南》主页
查看网页的body信息
response.body
查看每个每个章节的链接和名字
response.xpath('//dd/a[1]')
输出结果如图:
conan2.jpg
从输出结果可以看到,每个链接都已经提取出来了,但是没有显示a标签里面的内容。想要显示全,就需要extract()方法,转换成字符串输出,指令如下:
response.xpath('//dd/a[1]').extract()
输出结果如图:
conan3.jpg
如果我想保存每个章节的图片,需要哪些东西?
链接必不可少,当然还有每个章节的名字,我们要以文件夹的形式存储每个章节,文件夹的命名就是章节的名字,这样更规整。
我们使用text()获取每个章节的名字,指令如下:
response.xpath('//dd/a[1]/text()').extract()
我们使用@href获取每个章节的链接,指令如下:
response.xpath('//dd/a[1]/text()').extract()
2、分析每个章节里的内容
分析每个章节里的内容,看看如何获取每个图片的链接。
从这个网页我们要获取哪些信息?第一个当然还是图片的链接,第二个呢?将一个章节里的每个图片保存下来,我们如何命名图片?用默认名字下载下来的图片,顺序也就乱了。仔细一点的话,不难发现,第一页的链接为:http://comic.kukudm.com/comiclist/5/3389/1.htm,第二页的链接为:http://comic.kukudm.com/comiclist/5/3389/2.htm,第三页的链接为:http://comic.kukudm.com/comiclist/5/3389/3.htm 依此类推,所以我们可以根据这个规律进行翻页,而为了翻页,首先需要获取的就是每个章节的图片数,也就是页数,随后,我们根据每页的地址就可以为每个图片命名:第1页、第2页、第3页…,这样命名就可以了。不会出现乱序,并且很工整,方便我们阅读。由于有的章节图片的链接不是规律的,所以只能先获取页面地址,再获取图片地址,这样递进爬取。
使用ctrl+c退出之前的shell,分析章节页面,以第一章为例,使用指令如下:
scrapy shell "http://comic.kukudm.com/comiclist/5/3389/1.htm"
通过审查元素可以知道,页数存放在valign属性i为top的td标签中。获取的内容由于有好多信息,我们再使用re()方法,通过正则表达式获取页数。获取页数代码如下:
response.xpath('//td[@valign="top"]/text()').re('共(\d+)页')[0]
下面该获取图片的链接了,通过审查元素我们会发现,图片链接保存再img标签下的src属性中,理想状态,使用如下指令就可以获取图片链接:
response.xpath('//img[@id="comipic"]/@src').extract()
返回为空。这是为什么?通过response.body打印信息不难发现,这个链接是使用JS动态加载进去的。直接获取是不行的,网页分为静态页面和动态页面,对于静态页面好说,对于动态页面就复杂一些了。可以使用PhantomJS、发送JS请求、使用Selenium、运行JS脚本等方式获取动态加载的内容。(该网站动态加载方式简单,不涉及这些)
该网站是使用如下指令加载图片的:
document.write("<img id=comicpic name=comicpic src='"+server+"kuku3comic3/mztkn/Vol_01/0015A025H.jpg'><span style='display:none'><img src='"+server+"kuku3comic3/mztkn/Vol_01/00258044I.jpg'></span>");
JS脚本放在网页里,没有使用外部JS脚本,这就更好办了,直接获取脚本信息,不就能获取图片链接了?使用指令如下:
response.xpath('//script/text()').extract()
通过运行结果可以看出,我们已经获取到了图片链接,server的值是通过运行JS外部脚本获得的,但是这里,我们仔细观察server的值为http://n5.1whour.com/,其他页面也是一样,因此也就简化了流程。同样,记住这个指令,编写程序的时候会用到。
思路已经梳理清楚,需要的内容有章节链接、章节名、图片链接、每张页数。shell分析完毕。
三、Scrapy程序编写
1、Spiders程序测试
在cortoon/spiders目录下创建文件comic_spider.py
先打印章节链接地址看看,代码如下:
import scrapy
class ComicSpider(scrapy.Spider):
name = "comic"
allowed_domains = ['comic.kukudm.com']
start_urls = ['http://comic.kukudm.com/comiclist/5/']
def parse(self, response):
link_urls = response.xpath('//dd/a[1]/@href').extract()
for each_link in link_urls:
print('http://comic.kukudm.com' + each_link)
可使用如下指令运行工程来测试:
scrapy crawl comic
再打印章节名字看看,代码如下:
import scrapy
class ComicSpider(scrapy.Spider):
name = "comic"
allowed_domains = ['comic.kukudm.com']
start_urls = ['http://comic.kukudm.com/comiclist/5/']
def parse(self, response):
# link_urls = response.xpath('//dd/a[1]/@href').extract()
dir_names = response.xpath('//dd/a[1]/text()').extract()
for each_name in dir_names:
print(each_name)
2、Items编写
import scrapy
class ComicItem(scrapy.Item):
dir_name = scrapy.Field()
link_url = scrapy.Field()
img_url = scrapy.Field()
image_paths = scrapy.Field()
3、Settings编写
BOT_NAME = 'conan'
SPIDER_MODULES = ['conan.spiders']
NEWSPIDER_MODULE = 'conan.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'conan (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
'conan.pipelines.ComicImgDownloadPipeline': 1,
}
IMAGES_STORE = 'D:/DataguruPyhton/PythonSpider/images/名侦探柯南'
COOKIES_ENABLED = False
DOWNLOAD_DELAY = 0.25 # 250 ms of delay
- BOT_NAME:自动生成的内容,根名字;
- SPIDER_MODULES:自动生成的内容;
- NEWSPIDER_MODULE:自动生成的内容;
- ROBOTSTXT_OBEY:自动生成的内容,是否遵守robots.txt规则,这里选择不遵守;
- ITEM_PIPELINES:定义item的pipeline;
- IMAGES_STORE:图片存储的根路径;
- COOKIES_ENABLED:Cookie使能,这里禁止Cookie;
- DOWNLOAD_DELAY:下载延时,这里使用250ms延时。
4、Comic_spider编写
import re
import scrapy
from scrapy import Selector
from conan.items import ComicItem
class ComicSpider(scrapy.Spider):
name = 'comic'
def __init__(self):
# 图片链接server域名
self.server_img = 'http://n5.1whour.com/'
# 章节链接server域名
self.server_link = 'http://comic.kukudm.com'
self.allowed_domains = ['comic.kukudm.com']
self.start_urls = ['http://comic.kukudm.com/comiclist/5/']
# 匹配图片地址的正则表达式
self.pattern_img = re.compile(r'\+"(.+)\'><span')
# 从start_requests发送请求
def start_requests(self):
yield scrapy.Request(url=self.start_urls[0], callback=self.parse1)
# 解析response,获得章节图片链接地址
def parse1(self, response):
hxs = Selector(response)
items = []
# 章节链接地址
urls = hxs.xpath('//dd/a[1]/@href').extract()
# 章节名
dir_names = hxs.xpath('//dd/a[1]/text()').extract()
# 保存章节链接和章节名
for index in range(len(urls)):
item = ComicItem()
item['link_url'] = self.server_link + urls[index]
item['dir_name'] = dir_names[index]
items.append(item)
# 根据每个章节的链接,发送Request请求,并传递item参数
for item in items[-13:-1]:
yield scrapy.Request(url=item['link_url'], meta={'item': item}, callback=self.parse2)
# 解析获得章节第一页的页码数和图片链接
def parse2(self, response):
# 接收传递的item
item = response.meta['item']
# 获取章节的第一页的链接
item['link_url'] = response.url
hxs = Selector(response)
# 获取章节的第一页的图片链接
pre_img_url = hxs.xpath('//script/text()').extract()
# 注意这里返回的图片地址,应该为列表,否则会报错
img_url = [self.server_img + re.findall(self.pattern_img, pre_img_url[0])[0]]
# 将获取的章节的第一页的图片链接保存到img_url中
item['img_url'] = img_url
# 返回item,交给item pipeline下载图片
yield item
# 获取章节的页数
page_num = hxs.xpath('//td[@valign="top"]/text()').re(u'共(\d+)页')[0]
# 根据页数,整理出本章节其他页码的链接
pre_link = item['link_url'][:-5]
for each_link in range(2, int(page_num) + 1):
new_link = pre_link + str(each_link) + '.htm'
# 根据本章节其他页码的链接发送Request请求,用于解析其他页码的图片链接,并传递item
yield scrapy.Request(url=new_link, meta={'item': item}, callback=self.parse3)
# 解析获得本章节其他页面的图片链接
def parse3(self, response):
# 接收传递的item
item = response.meta['item']
# 获取该页面的链接
item['link_url'] = response.url
hxs = Selector(response)
pre_img_url = hxs.xpath('//script/text()').extract()
# 注意这里返回的图片地址,应该为列表,否则会报错
img_url = [self.server_img + re.findall(self.pattern_img, pre_img_url[0])[0]]
# 将获取的图片链接保存到img_url中
item['img_url'] = img_url
# 返回item,交给item pipeline下载图片
yield item
5、Pipelines编写
pipelines.py主要负责图片的下载,我们根据item保存的信息,进行图片的分类保存
from conan import settings
from scrapy import Request
import requests
import os
class ComicImgDownloadPipeline(object):
def process_item(self, item, spider):
# 如果获取了图片链接,进行如下操作
if 'img_url' in item:
images = []
# 文件夹名字
dir_path = '%s/%s' % (settings.IMAGES_STORE, item['dir_name'])
# 文件夹不存在则创建文件夹
if not os.path.exists(dir_path):
os.makedirs(dir_path)
# 获取每一个图片链接
for image_url in item['img_url']:
# 解析链接,根据链接为图片命名
houzhui = image_url.split('/')[-1].split('.')[-1]
qianzhui = item['link_url'].split('/')[-1].split('.')[0]
# 图片名
image_file_name = '第' + qianzhui + '页.' + houzhui
# 图片保存路径
file_path = '%s/%s' % (dir_path, image_file_name)
images.append(file_path)
if os.path.exists(file_path):
continue
# 保存图片
with open(file_path, 'wb') as handle:
response = requests.get(url=image_url)
for block in response.iter_content(1024):
if not block:
break
handle.write(block)
# 返回图片保存路径
item['image_paths'] = images
return item
6、运行程序
scrapy crawl comic
网友评论