使用scrapy编写爬虫的实践
流程概述
首先我想爬取某个网站的所有文章,但是由于我之前没有做过爬虫(而且不知道是哪种语言)最方便),所以想法是用python来创建一个爬虫(毕竟大家的名字都是爬虫的意思?)。我打算先把从网站搜索到的所有数据放到ElasticSearch
。我选择ElasticSearch
的原因是它速度快。它有分词插件和倒排索引。当需要数据的时候,查询效率会非常好(毕竟要查的东西还多吗?)然后我把所有的数据都放进ElasticSearch
老婆kibana
可视化数据并分析这些文章的内容。首先可以看看预期的可视化效果(如上图)。此渲染是kibana6 .4
系统提供的辅助渲染(即你可以这样做吗,我也想这样做吗?)。我稍后会发送 Dockerfile(还没有?)。
环境要求
- Jdk(Elasticsearch所需)
- ElasticSearch(用于数据存储)
- Kinaba(用于控制ElasticSearch和数据可视化) y
pip3 install scrapy
复制代码
- Redis(数据复制)
你可以去各自的教程来安装这些东西。我这里只安装了 ElasticSearch 吗?点击这里查看安装说明
第一步使用python pip安装需要的插件(第一个技巧在这里)
- tomd:将html转换为markdown❙re:需要Python redis插件
pip3 install redis
复制代码
- scrapy:安装框架(好)
- 首先我做了与上面相同的操作,我运行了
pip3 install scrapy 复制代码
- ,发现❙❀gⶶ❙ⶶ组件丢失了。
错误:命令'gcc'失败,退出状态为1
- 然后我找啊找,终于找到了正确的。解决方案(这期间你是否尝试过很多错误的答案?)。最终的解决方案是使用
yum
安装python34-devel
。这个python34-devel
取决于你自己的python版本。它可能是 python-devel。根据版本不同,将中间的34改为你的版本。我的是3.4.6
yum install python34-devel 复制代码
- 安装完成后,用scrapy命令试试。
第二步,使用scrapy创建项目
- 输入命令
scrapy startproject scrapyDemo
,创建一个基本的浏览器项目
liaochengdeMacBook-Pro:scrapy liaocheng$ scrapy startproject scrapyDemo
New Scrapy project 'scrapyDemo', using template directory '/usr/local/lib/python3.7/site-packages/scrapy/templates/project', created in:
/Users/liaocheng/script/scrapy/scrapyDemo
You can start your first spider with:
cd scrapyDemo
scrapy genspider example example.com
liaochengdeMacBook-Pro:scrapy liaocheng$
复制代码
- 使用genspider命令生成scrapy genspider demo juejin.im,下一个URL是你要爬取的网站,我们先爬自己的吧? start_urls
# -*- coding: utf-8 -*-
import scrapy
class DemoSpider(scrapy.Spider):
name = 'demo' ## 爬虫的名字
allowed_domains = ['juejin.im'] ## 需要过滤的域名,也就是只爬这个网址下面的内容
def start_requests(self):
start_urls = ['http://juejin.im/'] ## 初始url链接
for url in start_urls:
# 调用parse
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response): ## 如果新建的spider必须实现这个方法
pass
复制代码
- 在实体文件中写入
articleItem.py
(item类似于java实体文件)
import scrapy
class ArticleItem(scrapy.Item): ## 需要实现scrapy.Item文件
# 文章id
id = scrapy.Field()
# 文章标题
title = scrapy.Field()
# 文章内容
content = scrapy.Field()
# 作者
author = scrapy.Field()
# 发布时间
createTime = scrapy.Field()
# 阅读量
readNum = scrapy.Field()
# 点赞数
praise = scrapy.Field()
# 头像
photo = scrapy.Field()
# 评论数
commentNum = scrapy.Field()
# 文章链接
link = scrapy.Field()
复制代码
- 为方法
parse
def parse(self, response):
# 获取页面上所有的url
nextPage = response.css("a::attr(href)").extract()
# 遍历页面上所有的url链接,时间复杂度为O(n)
for i in nextPage:
if nextPage is not None:
# 将链接拼起来
url = response.urljoin(i)
# 必须是掘金的链接才进入
if "juejin.im" in str(url):
# 存入redis,如果能存进去,就是一个没有爬过的链接
if self.insertRedis(url) == True:
# dont_filter作用是是否过滤相同url true是不过滤,false为过滤,我们这里只爬一个页面就行了,不用全站爬,全站爬对对掘金不是很友好,我么这里只是用来测试的
yield scrapy.Request(url=url, callback=self.parse,headers=self.headers,dont_filter=False)
# 我们只分析文章,其他的内容都不管
if "/post/" in response.url and "#comment" not in response.url:
# 创建我们刚才的ArticleItem
article = ArticleItem()
# 文章id作为id
article['id'] = str(response.url).split("/")[-1]
# 标题
article['title'] = response.css("#juejin > div.view-container > main > div > div.main-area.article-area.shadow > article > h1::text").extract_first()
# 内容
parameter = response.css("#juejin > div.view-container > main > div > div.main-area.article-area.shadow > article > div.article-content").extract_first()
article['content'] = self.parseToMarkdown(parameter)
# 作者
article['author'] = response.css("#juejin > div.view-container > main > div > div.main-area.article-area.shadow > article > div:nth-child(6) > meta:nth-child(1)::attr(content)").extract_first()
# 创建时间
createTime = response.css("#juejin > div.view-container > main > div > div.main-area.article-area.shadow > article > div.author-info-block > div > div > time::text").extract_first()
createTime = str(createTime).replace("年", "-").replace("月", "-").replace("日","")
article['createTime'] = createTime
# 阅读量
article['readNum'] = int(str(response.css("#juejin > div.view-container > main > div > div.main-area.article-area.shadow > article > div.author-info-block > div > div > span::text").extract_first()).split(" ")[1])
# 点赞数
article['badge'] = response.css("#juejin > div.view-container > main > div > div.article-suspended-panel.article-suspended-panel > div.like-btn.panel-btn.like-adjust.with-badge::attr(badge)").extract_first()
# 评论数
article['commentNum'] = response.css("#juejin > div.view-container > main > div > div.article-suspended-panel.article-suspended-panel > div.comment-btn.panel-btn.comment-adjust.with-badge::attr(badge)").extract_first()
# 文章链接
article['link'] = response.url
# 这个方法和很重要(坑),之前就是由于执行yield article, pipeline就一直不能获取数据
yield article
# 将内容转换成markdown
def parseToMarkdown(self, param):
return tomd.Tomd(str(param)).markdown
# url 存入redis,如果能存那么就没有该链接,如果不能存,那么就存在该链接
def insertRedis(self, url):
if self.redis != None:
return self.redis.sadd("articleUrlList", url) == 1
else:
self.redis = self.redisConnection.getClient()
self.insertRedis(url)
复制代码
- 编写管道类。这根管子就是一根管子。所有由yield关键字返回的数据都可以传递到这个通道进行处理,但是需要在settings中进行配置。 Pipeline才起作用
from elasticsearch import Elasticsearch
class ArticlePipelines(object):
# 初始化
def __init__(self):
# elasticsearch的index
self.index = "article"
# elasticsearch的type
self.type = "type"
# elasticsearch的ip加端口
self.es = Elasticsearch(hosts="localhost:9200")
# 必须实现的方法,用来处理yield返回的数据
def process_item(self, item, spider):
# 这里是判断,如果是demo这个爬虫的数据才处理
if spider.name != "demo":
return item
result = self.checkDocumentExists(item)
if result == False:
self.createDocument(item)
else:
self.updateDocument(item)
# 添加文档
def createDocument(self, item):
body = {
"title": item['title'],
"content": item['content'],
"author": item['author'],
"createTime": item['createTime'],
"readNum": item['readNum'],
"praise": item['praise'],
"link": item['link'],
"commentNum": item['commentNum']
}
try:
self.es.create(index=self.index, doc_type=self.type, id=item["id"], body=body)
except:
pass
# 更新文档
def updateDocument(self, item):
parm = {
"doc" : {
"readNum" : item['readNum'],
"praise" : item['praise']
}
}
try:
self.es.update(index=self.index, doc_type=self.type, id=item["id"], body=parm)
except:
pass
# 检查文档是否存在
def checkDocumentExists(self, item):
try:
self.es.get(self.index, self.type, item["id"])
return True
except:
return False
复制代码
第四步运行代码看效果
- 使用
scrapy list
列出本地所有爬虫❙♝使用❙♝使用♝运行爬虫
scrapy crawl demo
复制代码
- 进入kibana并显示爬取的数据,执行以下命令即可显示数据
GET /article/_search
{
"query": {
"match_all": {}
}
}
复制代码
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "article2",
"_type": "type",
"_id": "5c790b4b51882545194f84f0",
"_score": 1,
"_source": {}
}
]
}
}
作者:fallinjava
链接:https://juejin.im/post/5c88bb19f265da2d9:※3d9掘金
版权 所有权利归作者所有。商业转载请联系作者获取授权。非商业转载请注明出处。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。