一步步教你用python的scrapy编写一个爬虫


Posted in Python onApril 17, 2019

介绍

本文将介绍我是如何在python爬虫里面一步一步踩坑,然后慢慢走出来的,期间碰到的所有问题我都会详细说明,让大家以后碰到这些问题时能够快速确定问题的来源,后面的代码只是贴出了核心代码,更详细的代码暂时没有贴出来。

流程一览

首先我是想爬某个网站上面的所有文章内容,但是由于之前没有做过爬虫(也不知道到底那个语言最方便),所以这里想到了是用python来做一个爬虫(毕竟人家的名字都带有爬虫的含义?),我这边是打算先将所有从网站上爬下来的数据放到ElasticSearch里面, 选择ElasticSearch的原因是速度快,里面分词插件,倒排索引,需要数据的时候查询效率会非常好(毕竟爬的东西比较多?),然后我会将所有的数据在ElasticSearch的老婆kibana里面将数据进行可视化出来,并且分析这些文章内容,可以先看一下预期可视化的效果(上图了),这个效果图是kibana6.4系统给予的帮助效果图(就是说你可以弄成这样,我也想弄成这样?)。后面我会发一个dockerfile上来(现在还没弄?)。

一步步教你用python的scrapy编写一个爬虫

环境需求

  • Jdk (Elasticsearch需要)
  • ElasticSearch (用来存储数据)
  • Kinaba (用来操作ElasticSearch和数据可视化)
  • Python (编写爬虫)
  • Redis (数据排重)

这些东西可以去找相应的教程安装,我这里只有ElasticSearch的安装?点我获取安装教程

第一步,使用python的pip来安装需要的插件(第一个坑在这儿)

1.tomd:将html转换成markdown

pip3 install tomd

2.redis:需要python的redis插件

pip3 install redis

3.scrapy:框架安装(坑)

  1、首先我是像上面一样执行了

pip3 install scrapy

   2、然后发现缺少gcc组件 error: command 'gcc' failed with exit status 1

一步步教你用python的scrapy编写一个爬虫

   3、然后我就找啊找,找啊找,最后终于找到了正确的解决方法(期间试了很多错误答案?)。最终的解决办法就是使用yum来安装python34-devel,  这个python34-devel根据你自己的python版本来,可能是python-devel,是多少版本就将中间的34改成你的版本, 我的是3.4.6

yum install python34-devel

   4、安装完成过后使用命令 scrapy 来试试吧。

一步步教你用python的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来生成一个基础的spider,使用命令scrapy genspider demo juejin.im, 后面这个网址是你要爬的网站,我们先爬自己家的?

liaochengdeMacBook-Pro:scrapy liaocheng$ scrapy genspider demo juejin.im
Created spider 'demo' using template 'basic'
liaochengdeMacBook-Pro:scrapy liaocheng$

查看生成的目录结构

一步步教你用python的scrapy编写一个爬虫

第三步,打开项目,开始编码

查看生成的的demo.py的内容

# -*- coding: utf-8 -*-
import scrapy


class DemoSpider(scrapy.Spider):
 name = 'demo' ## 爬虫的名字
 allowed_domains = ['juejin.im'] ## 需要过滤的域名,也就是只爬这个网址下面的内容
 start_urls = ['https://juejin.im/post/5c790b4b51882545194f84f0'] ## 初始url链接

 def parse(self, response): ## 如果新建的spider必须实现这个方法
 pass

可以使用第二种方式,将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)

编写pipeline类,这个pipeline是一个管道,可以将所有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查看本地的所有爬虫

liaochengdeMacBook-Pro:scrapyDemo liaocheng$ scrapy list
demo
liaochengdeMacBook-Pro:scrapyDemo liaocheng$

使用scrapy crawl demo来运行爬虫

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": {}
 }
 ]
 }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python 函数基础知识汇总
Mar 09 Python
Python中的pack和unpack的使用
Mar 12 Python
Django Sitemap 站点地图的实现方法
Apr 29 Python
Django CBV类的用法详解
Jul 26 Python
python验证码图片处理(二值化)
Nov 01 Python
python创建学生成绩管理系统
Nov 22 Python
Python爬虫库BeautifulSoup的介绍与简单使用实例
Jan 25 Python
解决django无法访问本地static文件(js,css,img)网页里js,cs都加载不了
Apr 07 Python
用opencv给图片换背景色的示例代码
Jul 08 Python
Python如何操作docker redis过程解析
Aug 10 Python
python实现AHP算法的方法实例(层次分析法)
Sep 09 Python
BeautifulSoup获取指定class样式的div的实现
Dec 07 Python
Python中如何导入类示例详解
Apr 17 #Python
Linux上使用Python统计每天的键盘输入次数
Apr 17 #Python
python3转换code128条形码的方法
Apr 17 #Python
Python爬虫——爬取豆瓣电影Top250代码实例
Apr 17 #Python
Python2与Python3的区别实例总结
Apr 17 #Python
详解Python用户登录接口的方法
Apr 17 #Python
详解python中递归函数
Apr 16 #Python
You might like
PHP学习笔记(一):基本语法之标记、空白、和注释
2015/04/17 PHP
js直接编辑当前cookie的脚本
2008/09/14 Javascript
javascript Ext JS 状态默认存储时间
2009/02/15 Javascript
仅IE不支持setTimeout/setInterval函数的第三个以上参数
2011/05/25 Javascript
对于this和$(this)的个人理解
2013/09/08 Javascript
js replace替换所有匹配的字符串
2014/02/13 Javascript
table行随鼠标移动变色示例
2014/05/07 Javascript
原生js实现模拟滚动条
2015/06/15 Javascript
Bootstrap4一次重大更新 几乎涉及每行代码
2016/05/16 Javascript
为什么JavaScript没有块级作用域
2016/05/22 Javascript
原生js获取left值和top值的三种方法
2017/08/02 Javascript
静态页面实现 include 引入公用代码的示例
2017/09/25 Javascript
JS中图片压缩的方法小结
2017/11/14 Javascript
跨域请求两种方法 jsonp和cors的实现
2018/11/11 Javascript
微信小程序云开发(数据库)详解
2019/05/17 Javascript
vue 兄弟组件的信息传递的方法实例详解
2019/08/30 Javascript
vue实现购物车功能(商品分类)
2020/04/20 Javascript
JavaScript实现网页留言板功能
2020/11/23 Javascript
Python基于pillow判断图片完整性的方法
2016/09/18 Python
Python合并字典键值并去除重复元素的实例
2016/12/18 Python
Python实现PS滤镜特效之扇形变换效果示例
2018/01/26 Python
Python实现批量读取图片并存入mongodb数据库的方法示例
2018/04/02 Python
Django 视图层(view)的使用
2018/11/09 Python
详解python配置虚拟环境
2019/04/08 Python
如何在Python 游戏中模拟引力
2020/03/27 Python
读取nii或nii.gz文件中的信息即输出图像操作
2020/07/01 Python
面向新手解析python Beautiful Soup基本用法
2020/07/11 Python
html5定制表单_动力节点Java学院整理
2017/07/11 HTML / CSS
HTML5新增form控件和表单属性实例代码详解
2019/05/15 HTML / CSS
Prototype中如何为一个元素添加一个方法
2014/12/08 面试题
2014年个人师德工作总结
2014/12/04 职场文书
地心历险记观后感
2015/06/15 职场文书
高中语文教学反思范文
2016/02/16 职场文书
nginx处理http请求实现过程解析
2021/03/31 Servers
SQL实现LeetCode(196.删除重复邮箱)
2021/08/07 MySQL
使用pipenv管理python虚拟环境的全过程
2021/09/25 Python