抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

目标网站传送门

CrawlSpider爬虫的创建

为什么要有CrawlSpider爬虫

spider是Scrapy框架中的基础爬虫,在翻页的时候,我们是这样操作的:

1
2
3
4
5
6
# 获取下一页
next_href = response.xpath("//a[@id='amore']/@href").get()
if next_href:
next_url = response.urljoin(next_href)
request = scrapy.Request(next_url)
yield request

而比spider高级一点的CrawlSpider爬虫,其主要特色是不用手动yield,可以实现遇到指定URL后自动翻页,这就比spider方便一些。

创建CrawlSpider爬虫的命令:

1
scrapy genspider -t crawl [爬虫名字] [域名]

参考上面的创建流程,我们在终端中输入下面四行代码:

1
2
3
4
5
cd /Users/pangyuxuan/lyCrawlSpider # cd到文件夹lyCrawlSpider
scrapy startproject lycs # 创建Scrapy项目,项目名称为lycs
cd lycs # 进入项目路径
scrapy genspider -t crawl lycSpider https://www.lieyunwang.com/
# 创建crawl爬虫,爬虫名称为lycSpider,目标域名为https://www.lieyunwang.com/

得到了这样的爬虫文件:

在这里插入图片描述


与spider的区别——“规则”的定义

spiders文件夹中的lycSpider.py与基础案例中的gsw_spider.py相对应,其默认的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class LycspiderSpider(CrawlSpider):
name = 'lycSpider' # 没变
allowed_domains = ['https://www.lieyunwang.com/'] # 没变
start_urls = ['http://https://www.lieyunwang.com//'] # 没变

rules = ( # 满足rules时自动爬取,不用再手动yield
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)

def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item

其中:

LinkExtractor

使用LinkExtractor可以在页面中自动找到所有满足规则的url,实现自动的爬取。

1
class scrapy.linkextractors.LinkExtractor(allow = (),deny = (),allow_domains = (),deny_domains = (),deny_extensions = None,restrict_xpaths = (),tags = ('a','area'),attrs = ('href'),canonicalize = True,unique = True,process_value = None)

常用参数:

allow允许的url——所有满足这个正则表达式的url都会被提取。

deny禁止的url——所有满足这个正则表达式的url都不会被提取。

allow_domains允许的域名——只有在这个里面指定的域名的url才会被提取。

deny_domains禁止的域名——所有在这个里面指定的域名的url都不会被提取。

restrict_xpaths :使用xpath——和allow共同过滤链接。

Rule

用来定义这个url爬取后的处理方式,比如是否需要跟进,是否需要执行回调函数等。

1
class scrapy.spiders.Rule(link_extractor, callback = None, cb_kwargs = None, follow = None,process_links = None, process_request = None)

常用参数:

link_extractor :一个LinkExtractor对象,用于定义爬取规则

callback :满足这个规则的url,应该要执行哪个回调函数

follow :指定根据该规则从response中提取的链接是否需要跟进,也就是需不需要找这个链接的页面里还有没有其他符合要求的链接

process_links :从link_extractor中获取到链接后会传递给这个函数,用来过滤不需要爬取的链接


实操

先在settings.py里关闭协议、设置ua

1
2
3
4
5
6
7
8
9
# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent' : '我的user-'
}

我们打开猎云网主页https://www.lieyunwang.com/

调整start_urls

在这里插入图片描述

在页码1处点击检查,点击下方的链接进入第一页,复制此时浏览器内的链接即可:

1
start_urls = ['https://www.lieyunwang.com/latest/p1.html']

编写rules

我们的思路是:

找到每一页的链接,再从每一页里找每一篇文章的链接。

规则应该是这样:

1
2
3
4
rules = (
Rule(LinkExtractor(allow=r'/latest/p\d+\.html'), follow=True),
Rule(LinkExtractor(allow=r'/archives/\d+'), callback="parse_detail", follow=False),
)

其中:

  • 第一条规则用于找到每一页,因为页面的格式都是这样:

在这里插入图片描述

所以使用正则表达式匹配字符,即为/latest/p\d+\.html,其中:

  • 页码可能是两位数,所以用d+
  • .是特殊符号,需要额外加一个反斜杠\

此外,找到每一页并不是终点,我们还需要找这一页里的文章,也就是还需要从这一页里面找其他链接,所以follow=True

  • 第二条规则用于找每一页里的所有文章,因为文章的格式都是这样:

在这里插入图片描述

所以我们的正则表达式写为/archives/\d+

此外,我们找到文章以后,并不需要通过该文章找其他文章,所以follow=False,另外我们需要调用函数来获取它的内容了,所以callback="parse_detail",其中parse_detail是后面要写的函数

parse_detail函数里测试一下我们的rules写没写对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class LycspiderSpider(CrawlSpider):
name = 'lycSpider'
allowed_domains = ['lieyunwang.com']
start_urls = ['https://www.lieyunwang.com/latest/p1.html']

rules = (
Rule(LinkExtractor(allow=r'/latest/p\d+\.html'), follow=True),
Rule(LinkExtractor(allow=r'/archives/\d+'), callback="parse_detail", follow=False),
)

def parse_detail(self, response):
print("="*50)
print(response.url) # 输出找到的url来验证
print("="*50)

在这里插入图片描述

哈哈对了!不对我会写在博客里吗

ps. 运行方法与前面的运行方法一致,都是新建一个start.py文件,可以先跳到后面去看一下start.py文件怎么写,也可以省略这一步测试(反正它一定是对的就是了,哼)

数据解析与存储

方便起见,我们只爬取文章的标题title、导语conclude和段落内容content

数据解析和存储的方式与之前的完全一样,在这里直接给出操作流程,不做过多的赘述:

1. 使用xpath获取三个部分的内容

直接右键-检查-copy xpath即可

1
2
3
4
5
6
7
def parse_detail(self, response):
title = response.xpath('//*[@id="fixed_container"]/div[1]/div[2]/div[1]/h1/text()').getall()
title = "".join(title).strip()
content = response.xpath('//*[@id="main-text-id"]').getall()
content = "".join(content).strip()
conclude = response.xpath('//*[@id="fixed_container"]/div[1]/div[2]/div[3]').getall()
conclude = "".join(conclude).strip()

2. 在settings.py中解除piplines的注释

1
2
3
4
5
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'lycs.pipelines.LycsPipeline': 300,
}

3. 编写piplines.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from itemadapter import ItemAdapter
import json

class LycsPipeline:
def open_spider(self,spider):
self.fp = open("简讯.txt",'w',encoding='utf-8')

def process_item(self, item, spider):
self.fp.write(json.dumps(dict(item),ensure_ascii=False)+'\n')
return item

def close_spider(self,spider):
self.fp.close()

4. 编写items.py

1
2
3
4
5
import scrapy
class LycsItem(scrapy.Item):
title = scrapy.Field() # 标题
content = scrapy.Field() # 导语
conclude = scrapy.Field() # 结论

5. 在lycSPider.py里导入items并传入要保存的参数

1
2
3
4
5
6
7
... # 省略其他部分代码
from ..items import LycsItem
... # 省略其他部分代码
def parse_detail(self, response):
... # 省略其他部分代码
item = LycsItem(title=title,content=content,conclude=conclude)
return item # 写yield应该也可以

运行

CrawlSpider的运行与spider完全一样,都是在终端输入命令以运行,方便起见,我们还是编写start.py文件来实现在pycharm里的运行:

1
2
from scrapy import cmdline
cmdline.execute("scrapy crawl lycSpider".split(" "))

ps. 为了秀一把这里给出了另一种发送命令的方式,本质上与之前那一种是一样的。

直接成功!

在这里插入图片描述


总结

相比于spider,CrawlSpider的核心优势就是可以自己找新的页面,不用我们手动设置翻页方法。

最终的参考代码

lycSpider.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
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import LycsItem

class LycspiderSpider(CrawlSpider):
name = 'lycSpider'
allowed_domains = ['lieyunwang.com']
start_urls = ['https://www.lieyunwang.com/latest/p1.html']

rules = (
Rule(LinkExtractor(allow=r'/latest/p\d+\.html'), follow=True),
Rule(LinkExtractor(allow=r'/archives/\d+'), callback="parse_detail", follow=False),
)

def parse_detail(self, response):
title = response.xpath('//*[@id="fixed_container"]/div[1]/div[2]/div[1]/h1/text()').getall()
title = "".join(title).strip()
content = response.xpath('//*[@id="main-text-id"]').getall()
content = "".join(content).strip()
conclude = response.xpath('//*[@id="fixed_container"]/div[1]/div[2]/div[3]').getall()
conclude = "".join(conclude).strip()
item = LycsItem(title=title,content=content,conclude=conclude)
return item

items.py

1
2
3
4
5
import scrapy
class LycsItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
conclude = scrapy.Field()

piplines.py

1
2
3
4
5
6
7
8
9
10
11
from itemadapter import ItemAdapter
import json
class LycsPipeline:
def open_spider(self,spider):
self.fp = open("简讯.txt",'w',encoding='utf-8')

def process_item(self, item, spider):
self.fp.write(json.dumps(dict(item),ensure_ascii=False)+'\n')
return item
def close_spider(self,spider):
self.fp.close()

settings.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
BOT_NAME = 'lycs'

SPIDER_MODULES = ['lycs.spiders']
NEWSPIDER_MODULE = 'lycs.spiders'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent' : '我的user-agent'
}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'lycs.pipelines.LycsPipeline': 300,
}

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36'
}

评论