初步搭建并使用Scrapy框架
目录
目标
版本
实战
搭建框架
获取图片链接、书名、价格
通过管道下载数据
通过多条管道下载数据
下载多页数据
目标
掌握Scrapy框架的搭建及使用,本文以爬取当当网魔幻小说为案例做演示。
版本
Scrapy 2.12.0
实战
搭建框架
第一步:在D:\pytharm_workspace位置创建爬虫Scrapy项目。通过cmd在该目录执行Scrapy创建项目命令。dangdang是我的项目名称。
scrapy startproject dangdang
第二步:进入项目目录,并创建爬虫类。其中magic_novels是我自定义的爬虫程序名称,permit.mee.gov.cn表示要爬取的网站域名。
第三步:注释在settings文件中掉OBOTSTXT_OBEY协议。
#ROBOTSTXT_OBEY = True
第四步:打开Pycharm控制台,进入项目目录。设置start_urls为我们要爬取的首页。parse表示项目启动后会自动请求start_urls中的URL。所以我们在parse方法中调试输出,并运行项目。
import scrapy
class MagicNovelsSpider(scrapy.Spider):
name = "magic_novels"
allowed_domains = ["category.dangdang.com"]
start_urls = ["https://category.dangdang.com/cp01.03.40.00.00.00.html"]
def parse(self, response):
print(response.url)
print(response.text)
scrapy crawl magic_novels
第五步:此时会打印很多的无用信息,我们可以在settings.py文件中设置日志级别。再次启动项目后会发现页面干净了很多。
LOG_LEVEL = "WARNING"
scrapy crawl magic_novels
注意:如果多次请求导致可能会导致缓存出现,请使用以下命令:
scrapy crawl magic_novels --set HTTPCACHE_ENABLED=False
获取图片链接、书名、价格
第一步:通过xpath爬取价格、图片、书名,我们先来打印调试。此时发现图片的链接不对,思考是否是懒加载的一个反扒策略。
def parse(self, response):
'''
图片的链接:src=//ul[@id='component_59']/li//img/@src
图片的名称:alt=//ul[@id='component_59']/li//img/@alt
图书的价格:price=//ul[@id='component_59']/li//p[@class='price']/span
考虑到所有的数据都来源于//ul[@id='component_59']/li,所以我们可以复用li对象。
'''
li_list = response.xpath("//ul[@id='component_59']/li")
for li in li_list:
print(f'图片的链接:src={li.xpath(".//img/@src").extract_first()}')
print(f'图片的名称:alt={li.xpath(".//img/@alt").extract_first()}')
print(f'图书的价格:price={li.xpath(".//p[@class='price']/span[1]/text()").extract_first()}')
print("\n")
第二步: 刷新页面,在浏览器检查中查看第一个和最后一个,发现图片链接的初始接收属性并不是src,而是data-original,src是加载以后才代替data-original的。
第三步:修改src获取的方法,并再次运行项目。发现除了第一个图书的src为None,其他src都正常获取了。猜测:是不是第一个图书打开时没有使用懒加载。
第四步: 通过调试发现,确实如刚才的猜想一般,第一个图书的src没有使用懒加载。修改代码后再次调试,发现可以获取到第一个图书的链接。
def parse(self, response):
'''
图片的链接:src=//ul[@id='component_59']/li//img/@src
图片的名称:alt=//ul[@id='component_59']/li//img/@alt
图书的价格:price=//ul[@id='component_59']/li//p[@class='price']/span
考虑到所有的数据都来源于//ul[@id='component_59']/li,所以我们可以复用li对象。
'''
li_list = response.xpath("//ul[@id='component_59']/li")
for i , li in enumerate(li_list):
print(f'第{i+1}本书。')
src = li.xpath(".//img/@data-original").get()
if src is None:
src = li.xpath(".//img/@src").get()
alt = li.xpath(".//img/@alt").get()
price = li.xpath(".//p[@class='price']/span[1]/text()").get()
print(f'图片的链接:src={src}')
print(f'图片的名称:alt={alt}')
print(f'图书的价格:price={price}')
print("\n")
通过管道下载数据
第一步:打开items.py文件,配置字段。
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class DangdangItem(scrapy.Item):
# 图片
src = scrapy.Field()
# 书名
name = scrapy.Field()
# 价格
price = scrapy.Field()
第二步:将item类导入到爬虫程序。
import scrapy
from dangdang.items import DangdangItem
class MagicNovelsSpider(scrapy.Spider):
name = "magic_novels"
allowed_domains = ["category.dangdang.com"]
start_urls = ["https://category.dangdang.com/cp01.03.40.00.00.00.html"]
def parse(self, response):
'''
图片的链接:src=//ul[@id='component_59']/li//img/@src
图书的名称:alt=//ul[@id='component_59']/li//img/@alt
图书的价格:price=//ul[@id='component_59']/li//p[@class='price']/span
考虑到所有的数据都来源于//ul[@id='component_59']/li,所以我们可以复用li对象。
'''
li_list = response.xpath("//ul[@id='component_59']/li")
for i , li in enumerate(li_list):
print(f'第{i+1}本书。')
src = li.xpath(".//img/@data-original").get()
if src is None:
src = li.xpath(".//img/@src").get()
alt = li.xpath(".//img/@alt").get()
price = li.xpath(".//p[@class='price']/span[1]/text()").get()
print(f'图片的链接:src={src}')
print(f'图书的名称:alt={alt}')
print(f'图书的价格:price={price}')
print("\n")
#该对象要通过管道去下载,通过yield可以在每次获得book后立刻返回book给管道。
book=DangdangItem(src=src, alt=alt, price=price);
yield book
第三步:在settings.py中开启管道配置。管道可以有很多个并且有优先级,300是默认值,值越大优先级越小。
ITEM_PIPELINES = {
"dangdang.pipelines.DangdangPipeline": 300,
}
第四步:来到pipelines.py文件,其中process_item方法中的item就是我们刚才在爬虫程序配置的boot对象。我们可以打印测试效果。
class DangdangPipeline:
def process_item(self, item, spider):
print(type(item))
print(str(item))
return item
scrapy crawl magic_novels
思考:我们通过process_item可以获取到数据,但是每次循环获取数据再重新打开文件、写入数据,关闭文件明显不符合开发规范。
第五步:在pipelines.py文件中配置open_spider和close_spider方法,分别表示在爬虫程序执行前执行的方法和在爬虫程序执行之后执行的方法。我们可以打印日志测试。
class DangdangPipeline:
#在爬虫文件开始之前就执行的方法
def open_spider(self, spider):
print("++++")
def process_item(self, item, spider):
print(type(item))
print(str(item))
return item
#在爬虫文件执行之后再执行的方法
def close_spider(self, spider):
print("----")
scrapy crawl magic_novels
第六步: 下载JSON数据。
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import json
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class DangdangPipeline:
#在爬虫文件开始之前就执行的方法
def open_spider(self, spider):
self.fp=open("book.json","w",encoding="utf-8")
self.fp.write("[")
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False) + ",\n"
self.fp.write(line)
return item
#在爬虫文件执行之后再执行的方法
def close_spider(self, spider):
# 删除最后一个多余的逗号,并关闭 JSON 数组
self.fp.seek(self.fp.tell() - 3, 0)
self.fp.write("\n]")
self.fp.close()
scrapy crawl magic_novels
通过多条管道下载数据
第一步:在pipelines.py文件中定义新的管道类。
#下载图片
class DangdangDownloadImgPipeline:
# 在爬虫文件开始之前就执行的方法
def open_spider(self, spider):
pass
def process_item(self, item, spider):
print(item.get('src'))
url="http:"+item.get('src')
filename='C:/Users/Administrator/Desktop/test/'+sanitize_filename(item.get("alt"))+'.jpg'
urllib.request.urlretrieve(url=url,filename=filename)
return item
# 在爬虫文件执行之后再执行的方法
def close_spider(self, spider):
pass
def sanitize_filename(filename):
"""
替换 Windows 文件名中不合法的字符为下划线。
"""
# 定义 Windows 文件名不允许的字符
invalid_chars = r'[\\/:*?"<>|]'
# 使用正则表达式将非法字符替换为下划线
return re.sub(invalid_chars, '_', filename)
第二步:在settings.py中定义该管道类的优先级。
ITEM_PIPELINES = {
"dangdang.pipelines.DangdangPipeline": 300,
"dangdang.pipelines.DangdangDownloadImgPipeline": 300,
}
第三步:执行下载操作,可以看到JSON数据和图片都下载成功了。
scrapy crawl magic_novels
下载多页数据
思考:目前我们只是下载了第一页的数据,能否通过配置页码下载多个页面的数据呢?
第一步:去页面点击下一页,发现链接都差不多,区别在于pg后面的跟的页码。
https://category.dangdang.com/pg2-cp01.03.40.00.00.00.html
https://category.dangdang.com/pg3-cp01.03.40.00.00.00.html
第二步:在爬虫程序中,设置基础的url和页码,页码初始化为第一页。
class MagicNovelsSpider(scrapy.Spider):
name = "magic_novels"
allowed_domains = ["category.dangdang.com"]
start_urls = ["https://category.dangdang.com/cp01.03.40.00.00.00.html"]
base_url="https://category.dangdang.com/pg"
page_num=1;
第三步:在parse方法中递归请求当当网,每次请求都将url的页码改变。注意:递归逻辑写在循环之外。
import scrapy
from dangdang.items import DangdangItem
class MagicNovelsSpider(scrapy.Spider):
name = "magic_novels"
allowed_domains = ["category.dangdang.com"]
start_urls = ["https://category.dangdang.com/cp01.03.40.00.00.00.html"]
base_url="https://category.dangdang.com/pg"
page_num=1;
def parse(self, response):
'''
图片的链接:src=//ul[@id='component_59']/li//img/@src
图书的名称:alt=//ul[@id='component_59']/li//img/@alt
图书的价格:price=//ul[@id='component_59']/li//p[@class='price']/span
考虑到所有的数据都来源于//ul[@id='component_59']/li,所以我们可以复用li对象。
'''
li_list = response.xpath("//ul[@id='component_59']/li")
for i , li in enumerate(li_list):
print(f'第{i+1}本书。')
src = li.xpath(".//img/@data-original").get()
if src is None:
src = li.xpath(".//img/@src").get()
alt = li.xpath(".//img/@alt").get()
price = li.xpath(".//p[@class='price']/span[1]/text()").get()
print(f'图片的链接:src={src}')
print(f'图书的名称:alt={alt}')
print(f'图书的价格:price={price}')
print("\n")
#该对象要通过管道去下载,通过yield可以在每次获得book后立刻返回book给管道。
book=DangdangItem(src=src, alt=alt, price=price);
yield book
if self.page_num<3:
self.page_num+=1
url=self.base_url+str(self.page_num)+"-cp01.03.40.00.00.00.html";
#GET请求
yield scrapy.Request(url=url, callback=self.parse)
第四步:运行项目。发现可以正常下载前三页的数据。