scrapy学习之爬虫练习平台爬取

前言

为了练习 Scrapy,找了一个爬虫练习平台,网址为:https://scrape.center/,目前爬取了前十个比较简单的网站,在此感谢平台作者提供的练习平台。

环境搭建

开始爬取前,首先要先把环境搭建起来,Pycharm 新建项目 learnscrapy 和对应的虚拟环境,安装好 Scrapy 和 PyMysql,执行 scrapy startproject learnscrapy 新建一个 scrapy 项目,搭建环境的具体步骤就不详细说了,项目也开源到了 Github 上,地址在这

开始前的准备

由于新建好的项目都是默认设置,所以在开始分析对应网站之前先进行一番设置,方便后边的爬取。

设置robots规则

settings.py 文件中将 ROBOTSTXT_OBEY 的值改为 False。这个其实在目前来说不需要设置,因为目标网站并不存在 robot 文件,但是为了莫名其妙的防止少抓数据,还是养成习惯吧。

设置日志等级

默认日志等级是 DEBUG,会将所有的请求及状态打印出来,大规模爬取时日志很多且很乱,将下列设置添加到 settings.py 文件中过滤掉一部分日志。

1
2
import logging
LOG_LEVEL = logging.INFO

设置下载延迟

为了不对目标网站造成太大影响, 稍微降低下下载延迟,默认的下载延迟是 0,将 DOWNLOAD_DELAY 取消注释并且设置为 0.1。

关掉Telnet控制台

目前基本用过不到它,还是关了吧。将 TELNETCONSOLE_ENABLED 的值设置为 False

更改默认的请求头

为了让我们的请求更像正常请求,所以需要设置下默认的请求头,防止被 ban。

1
2
3
4
5
6
7
DEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, '
'like Gecko) Chrome/86.0.4240.75 Safari/537.36'
}

管道和中间件用到的时候再开启吧,准备工作到此就结束了。

数据库

准备好一个 MySQL 的 docker 容器,设置好数据库的账号和密码,并且新建一个名为 test 的数据库,运行项目根目录中的 test.sql 文件,会生成三个表,下边爬取时会用到。

开始爬取

ssr1

ssr1 说明如下:

ssr1:电影数据网站,无反爬,数据通过服务端渲染,适合基本爬虫练习。

由于是服务端渲染,那么数据肯定存在于 HTML 源码中,直接从源码中抓数据即可。

在 spiders 文件夹中新建一个ssr1.py 文件,写入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import scrapy


class SSR1(scrapy.Spider):
name = 'ssr1'

def start_requests(self):
urls = [
f'https://ssr1.scrape.center/page/{a}' for a in range(1, 11)
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)

def parse(self, response, **kwargs):
pass

这两个方法是我们需要实现的,start_requests 方法中存储了起始 URL,通过 yield 将请求发送给下载器,通过默认的 parse 方法处理返回的数据。

在解析数据前,还有几件事需要做,设置采集数据的字段,开启 item 管道,指定数据由谁来处理,并且将修改后的设置写入到 setting.py 文件中。

  1. 需要新建需要采集的数据所拥有的字段,在 items.py 文件中添加以下内容:
1
2
3
4
5
6
7
8
9
class SSR1ScrapyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
fraction = scrapy.Field()
country = scrapy.Field()
time = scrapy.Field()
date = scrapy.Field()
director = scrapy.Field()
  1. 开启 item 管道,在 settings.py 文件中将 ITEM_PIPELINES 取消注释,并且将内容更改为:
1
2
3
ITEM_PIPELINES = {
'learnscrapy.pipelines.SSR1Pipeline': 300,
}
  1. pipelines.py 文件中添加以下内容:
1
2
3
4
class SSR1Pipeline:
def process_item(self, item, spider):
print(item)
return item

然后在 ssr1.py 中写解析 HTML 源码的代码,通过 xpath 来获取对应的数据,通过 yield Request 的方式来请求下一个页面,使用 meta 参数来传递 item 数据,callback 参数指定回调函数。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import scrapy
from scrapy import Request

from learnscrapy.items import SSR1ScrapyItem


class SSR1(scrapy.Spider):
name = 'ssr1'

def start_requests(self):
urls = [
f'https://ssr1.scrape.center/page/{a}' for a in range(1, 11)
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)

def parse(self, response, **kwargs):
result = response.xpath('//div[@class="el-card item m-t is-hover-shadow"]')
for a in result:
item = SSR1ScrapyItem()
item['title'] = a.xpath('.//h2[@class="m-b-sm"]/text()').get()
item['fraction'] = a.xpath('.//p[@class="score m-t-md m-b-n-sm"]/text()').get().strip()
item['country'] = a.xpath('.//div[@class="m-v-sm info"]/span[1]/text()').get()
item['time'] = a.xpath('.//div[@class="m-v-sm info"]/span[3]/text()').get()
item['date'] = a.xpath('.//div[@class="m-v-sm info"][2]/span/text()').get()
url = a.xpath('.//a[@class="name"]/@href').get()
yield Request(url=response.urljoin(url), callback=self.parse_person, meta={'item': item})

def parse_person(self, response):
item = response.meta['item']
item['director'] = response.xpath(
'//div[@class="directors el-row"]//p[@class="name text-center m-b-none m-t-xs"]/text()').get()
yield item

然后在项目根据路中新建启动文件 main.py,正常情况下应该使用命令行启动爬虫,我们为了方便调试使用 Pycharm 来启动。在 main.py 文件中写入以下内容:

1
2
3
4
from scrapy.cmdline import execute

if __name__ == '__main__':
execute(['scrapy', 'crawl', 'ssr1'])

执行它,数据应该会以字典的形式被打印出来,因为我们在 item 管道中输出了它,暂时还没有写入到数据库中。

接下来在 pipelines.py 文件中添加将数据入库的代码,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class SSR1Pipeline:
def __init__(self):
self.conn: pymysql.connect
self.cur: pymysql.cursors.Cursor
self.queue = []
self.count = 0

def open_spider(self, spider):
self.conn = pymysql.connect(host='192.168.233.128', user='root', password='123456', db='test',
port=3306, charset='utf8')
self.cur = self.conn.cursor()

def close_spider(self, spider):
if len(self.queue) > 0:
self.insert_database()
self.cur.close()
self.conn.close()

def insert_database(self):
sql = "insert into ssr1 (country,date,director,fraction,time,title) values (%s,%s,%s,%s,%s,%s)"
self.cur.executemany(sql, self.queue)
self.queue.clear()
self.conn.commit()

def process_item(self, item, spider):
self.queue.append(
(item['country'], item['date'], item['director'], item['fraction'], item['time'], item['title']))
if len(self.queue) > 30:
self.insert_database()
return item

重新运行爬虫,完整的 100 条数据应该就会被写入到数据库中了。

完整的代码提交见https://github.com/libra146/learnscrapy/tree/ssr1

ssr2

ssr2 说明如下:

电影数据网站,无反爬,无 HTTPS 证书,适合用作 HTTPS 证书验证。

scrapy 默认不验证 HTTPS 证书,只会发出警告,所以抓取规则和 ssr1 应该是一致的,但是 ssr2 的后端服务可能有问题,我这里一直报 504 错误,浏览器也无法打开,暂时无法测试规则是否有效。

完整的代码提交见https://github.com/libra146/learnscrapy/tree/ssr2

ssr3

ssr3 说明如下:

电影数据网站,无反爬,带有 HTTP Basic Authentication,适合用作 HTTP 认证案例,用户名密码均为 admin。

由于此网站带有 Basic Authentication,所以需要在请求头中添加身份验证信息,在 scrapy 中使用下载中间件来实现。

其他代码和上面的两个相同,不一样的地方在于 ssr3 通过 custom_settings 指定了自定义的下载中间件。

1
2
3
4
5
6
7
8
class SSR3Spider(scrapy.Spider):
name = "ssr3"
# 覆盖全局设置中的设置,使用自定义的下载中间件
custom_settings = {
'DOWNLOADER_MIDDLEWARES': {
'learnscrapy.middlewares.SSR3DownloaderMiddleware': 543,
}
}

同时需要在 middlewares.py 中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class SSR3DownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.

@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s

def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.

# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
# 添加验证信息
request.headers.update({'authorization': 'Basic ' + base64.b64encode('admin:admin'.encode()).decode()})
return None

def process_response(self, request, response, spider):
# Called with the response returned from the downloader.

# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response

def process_exception(self, request, exception, spider):
# Called when a download handler or a process_request()
# (from other downloader middleware) raises an exception.

# Must either:
# - return None: continue processing this exception
# - return a Response object: stops process_exception() chain
# - return a Request object: stops process_exception() chain
pass

def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)

在请求被发出去之前添加验证信息到请求头中即可。但是在添加完验证信息后服务器仍然返回 504,猜测是因为 HTTP Basic Authentication 验证被配置在了 nginx 中, 后端服务器仍然处于离线状态,才导致了这种情况。

完整的代码提交见https://github.com/libra146/learnscrapy/tree/ssr3

ssr4

ssr4 说明如下:

电影数据网站,无反爬,每个响应增加了 5 秒延迟,适合测试慢速网站爬取或做爬取速度测试,减少网速干扰。

对于 scrapy 来说可以通过 ssr4 来测试爬取速度,排除网速干扰,对于爬取规则来说并没有区别,只是时间长了一点而已。代码基本无改动。

完整代码提交见https://github.com/libra146/learnscrapy/tree/ssr4

总结

以上就是所有的 ssr 爬取分析和代码,对于这种简单的无反爬措施并且还不限制爬取速度的网站还是比较简单的,有助于理解 scrapy 的写法和框架。

参考链接

https://docs.scrapy.org/en/latest/index.html

本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接https://blog.d77.xyz/archives/35dbd7c9.html