Scrapy爬虫
一、构建项目
cmd窗口,cd切换路径
scrapy startproject cartoon
进入第一级目录,也就是含有配置文件的一级
scrapy genspider example example.com
这里不需要给出http://头,会自动创建
scrapy genspider driver "http://comic.kukudm.com/comiclist/2491/index.htm"
然后spiders文件里面多了一个叫driver的py文件,里面已经帮我们写好了类的基本框架了
二、shell分析
没有创建项目也可以查看,用于分析网页结构
shell根据response提前初始化了变量 sel 。该selector根据response的类型自动选择最合适的分析规则(XML vs HTML)
scrapy shell "http://comic.kukudm.com/comiclist/2491/index.htm"
之后可以查看网页标签信息,这里打印的是浏览器的所有Elements
response.body
查看包头
response.headers
提取内容
首先我们先提取每个章节的名字和链接
是这样的,我们得到的response,需要转成Selector对象,就像构造BeautifulSoup对象那样。可以指定text,也可以指定response
Selector(self, response=None, text=None, type=None, root=None, _root=None, **kwargs)
但是为了方便,已经以response属性的形式.selector映射了选择器,可以直接对response使用xpath。
response.xpath("//dd/a[1]/text()")
response.xpath("//dd/a[1]/@href")
这个时候返回的Selector列表,<Selector xpath='//dd/a[1]/text()' data='求你揉一揉吧 序'>
PS: 这是排行榜第一的漫画,我没有别的意思,真的没有。。。
text()表示提取文字内容,我们需要把弄出来,要用到 extract()
response.xpath("//dd/a[1]/text()").extract()
# ['求你揉一揉吧 序', '求你揉一揉吧 1话', ...]
# ['/comiclist/2491/64789/1.htm', '/comiclist/2491/64790/1.htm', ...]
使用ctrl+c退出之前的shell,现在分析章节页面
我们需要得到每个章节的总页数,提取文本信息后使用re()方法,返回匹配的字符串列表,相当于findall
注意这里不能再text()后提取文字,否则成了单独的字符串,不是Selector对象,不能直接使用re方法了。
scrapy shell "http://comic.kukudm.com/comiclist/2491/64789/1.htm"
pages = response.xpath("//td[@valign='top']/text()")[0].re(r"共(\d+)")[0]
接下来获取图片链接,发现结果不太对,仔细一看,前面有javascript动作,真实的链接是动态加载进去的,我们直接提取的是html和css,就不能向我们这样简单提取了。好在JS里有相关链接信息,我们改成提取script标签。负载的动态加载之后有空研究一下。
response.xpath("//td[@valign='top']/img[1]/@src")
# [<Selector xpath="//td[@valign='top']/img[1]/@src" data='/images/t2.gif'>]
抓取第一条JS脚本,提取document.write,找到有用的链接了。
应该就是这条后缀,newkuku/2018/08/07/求你揉一揉吧_第00话/000166C.jpg,对比img的src,server就应该是n5.1whour.com
response.xpath("//td[@valign='top']/script[1]/text()").extract()
# ['\r\ndocument.write("<IMG SRC=\'"+m201304d+"newkuku/2018/08/07/求你揉一揉吧_第00话
# /000166C.jpg\'><span style=\'display:none\'><img src=\'"+m201304d+"newkuku/2018/08
# /07/求你揉一揉吧_第00话/000262D.jpg\'></span>");\r\n']
三、Scrapy编写
1.spider初探
import scrapy
class DriverSpider(scrapy.Spider):
name = 'driver'
allowed_domains = ['comic.kukudm.com']
start_urls = ['http://http://comic.kukudm.com/comiclist/2491/index.htm/']
def parse(self, response):
link_urls = response.xpath("//dd/a[1]/@href").extract()
for each in link_urls:
print("http://comic.kukudm.com" + each)
- name:自己定义的内容,在运行工程的时候需要用到的标识;
- allowed_domains:允许爬虫访问的域名,防止爬虫跑飞。这个域名需要放到列表里;
- start_urls:开始爬取的url,同样这个url链接也需要放在列表里;
def parse(self, response) :请求分析的回调函数,如果不定义start_requests(self),获得的请求直接从这个函数分析;这两个函数是从scrapy.Spider继承下来的,所以会有自动补全。
在cmd调用我们写好的spider进行爬取
注意:这个和spider类里的name一致
scrapy crawl driver
2.items编写
item呢,跟字典用法差不多。scrapy.Field()创建了Field对象,且没有被赋值,那么就将作为item的键值。在cmd里import items后,可以这样创造一个对象。
>>> pro = items.CarItem(chap_name="nice")
>>> pro
{'chap_name': 'nice'}
>>> pro.fields
{'chap_link': {}, 'chap_name': {}}
>>> pro.items()
ItemsView({'chap_name': 'nice'})
import scrapy
class CarItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
chapter_name = scrapy.Field()
chapter_url = scrapy.Field()
img_url = scrapy.Field()
img_paths = scrapy.Field()
3.settings编写
BOT_NAME = 'cartoon'
SPIDER_MODULES = ['cartoon.spiders']
NEWSPIDER_MODULE = 'cartoon.spiders'
ROBOTSTXT_OBEY = True
# 是否遵守robotstxt协议
IMAGES_STORE = "F:/cartoon"
# 文件存储根路径
DOWNLOAD_DELAY = 0.25
COOKIES_ENABLED = False
ITEM_PIPELINES = {
'cartoon.pipelines.CartoonPipeline': 1,
}
# 数字大小表示pipeline执行先后顺序,0-1000
4.spider编写
好像有点问题,有空来改,现在忙比赛了。
import scrapy
import re
from car.items import CarItem
class DriverSpider(scrapy.Spider):
name = 'driver'
img_server = "http://n5.1whour.com/"
server_link = "http://comic.kukudm.com"
allowed_domains = ['comic.kukudm.com']
start_urls = ['http://comic.kukudm.com/comiclist/2491']
pattern = re.compile(r'\+"(.+)\'><span')
def start_requests(self):
yield scrapy.Request(url=DriverSpider.start_urls[0], callback=self.parse)
def parse(self, response):
chap_links = response.xpath("//dd/a[1]/@href").extract()
chap_names = response.xpath("//dd/a[1]/text()").extract()
item = CarItem()
for index in range(len(chap_links)):
item["chap_name"] = chap_names[index]
item["chap_link"] = DriverSpider.server_link + chap_links[index]
yield scrapy.Request(url=item["chap_link"], meta={"item": item}, callback=self.parse_chap)
def parse_chap(self, response):
item = response.meta["item"]
pre_img_url = response.xpath("//td[@valign='top']/script[1]/text()").extract()
first_img_link = DriverSpider.img_server + DriverSpider.pattern.findall(pre_img_url)[0]
item["img_url"] = [first_img_link]
yield item
page_num = int(response.xpath("//td[@valign='top']/text()")[0].re(r"共(\d+)")[0])
img_prefix = first_img_link[: first_img_link.rfind("/") + 1]
for img_order in range(2, page_num + 1):
behind_img = img_prefix + str(img_order) + ".htm"
yield scrapy.Request(url=behind_img, meta={"item": item}, callback=self.parse_img)
def parse_img(self, response):
item = response.meta["item"]
item["chap_url"] = response.url
pre_img_url = response.xpath("//td[@valign='top']/script[1]/text()").extract()
behind_img_link = DriverSpider.img_server + DriverSpider.pattern.findall(pre_img_url)[0]
item["img_url"] = [behind_img_link]
yield item
5.pipeline编写
yield出来的item就到这来处理了。可以手动实现两个函数,跟spider的启动和关闭相关联的。
from car import settings
from scrapy import Request
import requests
import os
class CarPipeline(object):
def process_item(self, item, spider):
if "img_url" in item:
imges = []
dir_path = "%s/%s" % (settings.IMAGES_STORE, item["chap_name"])
if not os.path.exists(dir_path):
os.makedirs(dir_path)
for img_url in item["img_url"]:
tail = img_url.split(".")[-1]
order = item["chap_name"].split("/")[-1].split(".")[0]
img_file_name = "第" + order + "页" + tail
file_path = "%s/%s" % (dir_path, img_file_name)
imges.append(file_path)
if os.path.exists(file_path):
continue
with open(file_path, "wb") as handle:
response = requests.get(img_url)
for block in response.iter_content(1024):
if not block:
break
handle.write(block)
item["img_path"] = imges
return item