我们知道,对于scrapy框架来说,不仅可以单机构建复杂的爬虫项目,还可以通过简单的修改,将单机版爬虫改为分布式的,大大提高爬取效率。下面我就以一个简单的爬虫案例,介绍一下如何构建一个单机版的爬虫,并做简单修改,使其实现分布式功能。
需求分析
爬取的链接为点我
- 访问页面,并实现1-10页的页面爬取,并保存源码到htmls目录下
- 解析页面,并获取到图片链接,并下载图片,保存到images目录下
单机版爬虫
准备爬虫项目
使用命令构建爬虫项目
在自己的放置爬虫的目录,或新目录内运行命令scrapy startproject scrapyMovieDemo
创建一个scrapy工程
效果如下:
使用命令构建爬虫
使用cd scrapyMovieDemo
命令进入已经创建的爬虫项目目录
运行scrapy genspider mv_spider_single ssr4.scrape.center
命令创建基础爬虫
效果如下:
项目目录结构介绍
下面我们来看一下创建爬虫工程与创建爬虫过程中,我们的工程与项目文件结构
如下:
最外层是一个名为scrapyMovieDemo的目录
里面是一个与爬虫工程同名的目录,还有一个scrapy.cfg文件
如下:
同名目录下有一个spiders目录,里面包含所有爬虫文件【刚刚创建的mv_spider_single就在spiders目录里面】
同名目录下有items.py,middlerwares.py,pipelines.py,settings.py文件(这里只对整个爬虫开发过程中用到的文件代码进行解释)。【这些文件的介绍,请看这里简单介绍】
创建数据保存目录
在scrapy.cfg文件的同级目录创建htmls和images两个文件
创建后的结果如下:
单机爬虫代码实现
下面我通过修改mv_spider_single.py文件,实现单机爬虫!
基础代码讲解
开发前,我们打开mv_spider_single.py文件,里面已经含有了一些基本的代码【如上图】。
以下是代码的逐行解释:
class MvSpiderSingleSpider(scrapy.Spider)
定义了一个名为 MvSpiderSingleSpider 的 Spider 类,继承自 Scrapy 的 Spider 类。
name = "mv_spider_single"
设置了爬虫的名称为 “mv_spider_single”,用于在 Scrapy 中标识该爬虫。
allowed_domains = ["ssr4.scrape.center"]
指定了允许爬取的域名,即 “ssr4.scrape.center”。
start_urls = ["http://ssr4.scrape.center/"]
设置了起始URL,即爬虫启动时首先请求的URL,这里是 “http://ssr4.scrape.center/”。
def parse(self, response)
定义了 parse 方法,用于处理响应。在这个示例中,该方法为空,没有具体的爬取逻辑。
这个爬虫的功能是访问 “http://ssr4.scrape.center/” 网站,但由于 parse 方法为空,没有实际的数据爬取操作。
运行基础代码
这个scrapy自动生成的代码是可以运行的,下面我们来运行一下,看看输出什么东西。
我们在scrapy.cfg的同级目录下,运行命令scrapy crawl mv_spider_single
即可运行爬虫代码。代码运行结果如下【这个结果大致浏览一下就可以,这里只是用于演示使用命令运行代码】:
(310) PS C:\Users\epro\Desktop\code\scrapy_demo\scrapyMovieDemo> scrapy crawl mv_spider_single
2024-02-20 09:13:31 [scrapy.utils.log] INFO: Scrapy 2.8.0 started (bot: scrapyMovieDemo)
2024-02-20 09:13:31 [scrapy.utils.log] INFO: Versions: lxml 4.9.1.0, libxml2 2.9.12, cssselect 1.2.0, parsel 1.7.0, w3lib 2.1.1, Twisted 22.10.0, Python 3.10.9 | pac
kaged by Anaconda, Inc. | (main, Mar 1 2023, 18:18:15) [MSC v.1916 64 bit (AMD64)], pyOpenSSL 23.0.0 (OpenSSL 3.0.8 7 Feb 2023), cryptography 39.0.2, Platform Windo
ws-10-10.0.19045-SP0
2024-02-20 09:13:31 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'scrapyMovieDemo',
'FEED_EXPORT_ENCODING': 'utf-8',
'NEWSPIDER_MODULE': 'scrapyMovieDemo.spiders',
'REQUEST_FINGERPRINTER_IMPLEMENTATION': '2.7',
'ROBOTSTXT_OBEY': True,
'SPIDER_MODULES': ['scrapyMovieDemo.spiders'],
'TWISTED_REACTOR': 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'}
2024-02-20 09:13:31 [asyncio] DEBUG: Using selector: SelectSelector
2024-02-20 09:13:31 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor
2024-02-20 09:13:31 [scrapy.utils.log] DEBUG: Using asyncio event loop: asyncio.windows_events._WindowsSelectorEventLoop
2024-02-20 09:13:31 [scrapy.extensions.telnet] INFO: Telnet Password: 35be36c817707dbd
2024-02-20 09:13:31 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.logstats.LogStats']
2024-02-20 09:13:31 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2024-02-20 09:13:31 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2024-02-20 09:13:31 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2024-02-20 09:13:31 [scrapy.core.engine] INFO: Spider opened
2024-02-20 09:13:32 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2024-02-20 09:13:32 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2024-02-20 09:13:33 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (308) to <GET https://ssr4.scrape.center/robots.txt> from <GET http://ssr4.scrape.cent
er/robots.txt>
2024-02-20 09:13:34 [scrapy.core.engine] DEBUG: Crawled (404) <GET https://ssr4.scrape.center/robots.txt> (referer: None)
2024-02-20 09:13:34 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (308) to <GET https://ssr4.scrape.center/> from <GET http://ssr4.scrape.center/>
2024-02-20 09:13:40 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://ssr4.scrape.center/> (referer: None)
2024-02-20 09:13:40 [scrapy.core.engine] INFO: Closing spider (finished)
2024-02-20 09:13:40 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 932,
'downloader/request_count': 4,
'downloader/request_method_count/GET': 4,
'downloader/response_bytes': 43026,
'downloader/response_count': 4,
'downloader/response_status_count/200': 1,
'downloader/response_status_count/308': 2,
'downloader/response_status_count/404': 1,
'elapsed_time_seconds': 8.425927,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2024, 2, 20, 1, 13, 40, 478802),
'log_count/DEBUG': 7,
'log_count/INFO': 10,
'response_received_count': 2,
'robotstxt/request_count': 1,
'robotstxt/response_count': 1,
'robotstxt/response_status_count/404': 1,
'scheduler/dequeued': 2,
'scheduler/dequeued/memory': 2,
'scheduler/enqueued': 2,
'scheduler/enqueued/memory': 2,
'start_time': datetime.datetime(2024, 2, 20, 1, 13, 32, 52875)}
2024-02-20 09:13:40 [scrapy.core.engine] INFO: Spider closed (finished)
修改代码实现页面爬取
下面我通过修改mv_spider_single.py文件,实现单机爬虫。
import scrapy
class MvSpiderSingleSpider(scrapy.Spider):
name = "mv_spider_single"
allowed_domains = ["ssr4.scrape.center"]
# 定义类变量start_url,用于后续请求url的构造
start_url = 'https://ssr4.scrape.center/page/{}'
def start_requests(self):
# 定义开始爬取的请求,该方法会在爬虫启动时被调用
print("start_requests")
for page in range(1, 2):
# 拼接成可用的url,实现不同页面的链接构造
url = self.start_url.format(page)
# 构造请求并指定回调函数为 parse,该函数将在响应返回时被调用
yield scrapy.Request(url=url, method='GET', callback=self.parse)
print("send page requests", url)
def parse(self, response):
file_name = 'htmls/demo.html'
with open(file_name, 'wb') as f:
f.write(response.body)
print("页面保存完毕,请查看...")
以上的代码,构造了链接为https://ssr4.scrape.center/page/1的一个请求,并提交发送。
请求响应后,调用parse函数,将响应体保存为demo.html文件。
命令启动爬虫示例
我们在scrapy.cfg的同级目录下,运行命令scrapy crawl mv_spider_single
运行爬虫代码。
代码运行成功后,我们发现htmls目录下多了一个demo.html文件,里面包含刚刚请求的响应网页源码。
下面我们查看一下刚刚运行的日志:
到这里,我们一个简单的单页面scrapy单机版爬虫就实现啦!!!😃
修改代码实现多页爬取
这里我们只需要在构造url时指定多个页面参数,发送多个链接,即可实现多页爬取。
查看以下代码:
class MvSpiderSingleSpider(scrapy.Spider):
name = "mv_spider_single"
allowed_domains = ["ssr4.scrape.center"]
# 定义类变量start_url,用于后续请求url的构造
start_url = 'https://ssr4.scrape.center/page/{}'
# 定义起始页与结束页
start_page = 1
end_page = 10
def start_requests(self):
# 定义开始爬取的请求,该方法会在爬虫启动时被调用
print("start_requests")
for page in range(self.start_page, self.end_page+1):
# 拼接成可用的url,实现不同页面的链接构造
url = self.start_url.format(page)
# 构造请求并指定回调函数为 parse,该函数将在响应返回时被调用
yield scrapy.Request(url=url, method='GET', callback=self.parse,meta={"page":page})
print("send page requests", url)
def parse(self, response):
# file_name = 'htmls/demo.html'
# with open(file_name, 'wb') as f:
# f.write(response.body)
# print("页面保存完毕,请查看...")
page = response.meta.get('page')
print("page", page)
# 保存响应页面源码
file_name = 'htmls/mv_spider_{}.html'.format(page)
with open(file_name, 'wb') as f:
f.write(response.body)
这里我在类变量内定义了开始页码与结束页码,同时通过for循环构造多个url,并发送请求,并在发送请求时指定了meta参数,将页码传递到response内,用于后续爬虫处理。
在parse函数内,我取出meta内的page参数,同时将响应结果的源码保存到htmls内。
再次运行爬虫脚本,获取到的日志如下:
查看htmls文件夹内有10个刚刚请求的结果源码。
到这里,我们就实现了翻页请求的构造与代码运行测试。😋😋😋
解析页面深层爬取
说到解析页面,我们就要从html的结构开始说起
这是官方的解释:
HTML 结构是通过元素之间的嵌套关系来定义的。标签的嵌套和属性的使用共同构建了页面的结构和外观。开发者可以使用这些元素来创建丰富和交互性的网页。 HTML 提供了一种标准化的方式,使浏览器能够正确解释和呈现网页内容。
网页与html解析,可以看我这篇文章点我
这里我将使用xpath定位并解析数据。
xpath解析数据
- 打开链接点这里,打开f12,选择图片,右击检查
<img data-v-7f856186="" src="https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c" class="cover">
这里我们看到了图片链接为img标签的src属性
这里我们看到每个div标签包含了页面内的一行数据,可以想到我们只要使用xpath先定位到所有包含img标签的父级div标签,再遍历每个标签元素,提取img标签内的src属性即可。
构造深层爬虫
先在parse函数内添加页面解析代码,获取到图片链接
# 提取图片地址,并发送到队列用于爬虫
img_elements = response.xpath('//div[@class="el-card__body"]//img')
for img in img_elements:
src = img.xpath('@src').get()
img_name = src.split('/')[-1].split('@')[0]
# 发送图片请求
yield scrapy.Request(url=src, method='GET', callback=self.parse_img, meta={'img_name': img_name
})
print("解析完毕 发送图片请求")
再次构造解析函数,用于保存图片请求的响应【保存图片】
def parse_img(self, response):
img_name = response.meta['img_name']
file_name = 'imgs/{}'.format(img_name)
print("下载图片")
with open(file_name, 'wb') as f:
f.write(response.body)
完整的mv_spider_single.py文件里源码如下:
import scrapy
class MvSpiderSingleSpider(scrapy.Spider):
name = "mv_spider_single"
allowed_domains = ["ssr4.scrape.center"]
# 定义类变量start_url,用于后续请求url的构造
start_url = 'https://ssr4.scrape.center/page/{}'
# 定义起始页与结束页
start_page = 1
end_page = 10
def start_requests(self):
# 定义开始爬取的请求,该方法会在爬虫启动时被调用
print("start_requests")
for page in range(self.start_page, self.end_page+1):
# 拼接成可用的url,实现不同页面的链接构造
url = self.start_url.format(page)
# 构造请求并指定回调函数为 parse,该函数将在响应返回时被调用
yield scrapy.Request(url=url, method='GET', callback=self.parse,meta={"page":page})
print("send page requests", url)
def parse(self, response):
# file_name = 'htmls/demo.html'
# with open(file_name, 'wb') as f:
# f.write(response.body)
# print("页面保存完毕,请查看...")
page = response.meta.get('page')
print("page", page)
# 保存响应页面源码
file_name = 'htmls/mv_spider_{}.html'.format(page)
with open(file_name, 'wb') as f:
f.write(response.body)
# 提取图片地址,并发送到队列用于爬虫
img_elements = response.xpath('//div[@class="el-card__body"]//img')
for img in img_elements:
src = img.xpath('@src').get()
# 图片的文件名用链接里面的id来表示,如:ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg
img_name = src.split('/')[-1].split('@')[0]
# 发送图片请求
yield scrapy.Request(url=src, method='GET', callback=self.parse_img, meta={'img_name': img_name
})
print("解析完毕 发送图片请求")
def parse_img(self, response):
img_name = response.meta['img_name']
file_name = 'images/{}'.format(img_name)
print("下载图片")
with open(file_name, 'wb') as f:
f.write(response.body)
运行深层爬虫代码,处理问题
运行上面的爬虫代码,查看日志
2024-02-21 07:52:15 [scrapy.core.engine] DEBUG: Crawled (404) <GET https://ssr4.scrape.center/robots.txt> (referer: None)
2024-02-21 07:52:21 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://ssr4.scrape.center/page/1> (referer: None)
page 1
2024-02-21 07:52:21 [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to 'p0.meituan.net': <GET https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b
5cd7896cf62472.jpg@464w_644h_1e_1c>
解析完毕 发送图片请求
2024-02-21 07:52:21 [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to 'p1.meituan.net': <GET https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa
7e187c3df767253.jpg@464w_644h_1e_1c>
解析完毕 发送图片请求
发现images文件内并没有获取到图片,日志内有如上情况。
这其实是scrapy自带的过滤器将链接为非allowed_domains
下的域名下的请求过滤了,没有将其发送。要解决这个问题也很简单,只要在allowed_domains
下添加图片的域名即可
添加后的结果为
allowed_domains = ['ssr4.scrape.center','p0.meituan.net']
这样再次运行爬虫,查看日志
2024-02-21 08:00:48 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://p0.meituan.net/robots.txt> (referer: None)
2024-02-21 08:00:48 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e143840
6.jpg@464w_644h_1e_1c>
2024-02-21 08:00:48 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048
.jpg@464w_644h_1e_1c>
2024-02-21 08:00:48 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://p0.meituan.net/movie/1f0d671f6a37f9d7b015e4682b8b113e174332
.jpg@464w_644h_1e_1c>
发现依然没有下载到图片,这里Forbidden by robots.txt
原因是没有爬虫配置内遵守了robot协议。只要我们配置不遵守robot协议即可。
这里我们需要打开spiders目录的同级文件settings.py
,对里面的ROBOTSTXT_OBEY
参数设置为False
settings.py
内默认是True
再次运行爬虫。。
就可以获取到图片了。😆😆😆
images目录内容如下:
到这里我们的单机版爬虫就开发完成了,成功了一次!!😆😆😆😆😆
结束
1. List item
2. List item
3. 开发单机爬虫
4. 修改settings配置
5. 命令启动
6. 脚本启动
7. 评估与回顾