目标网站:传送门
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 = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), )
def parse_item(self, response): item = {} return item
|
其中:
使用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
| ROBOTSTXT_OBEY = False
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) 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
|
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
|
运行
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'
ROBOTSTXT_OBEY = False
DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', 'User-Agent' : '我的user-agent' }
ITEM_PIPELINES = { 'lycs.pipelines.LycsPipeline': 300, }
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' }
|