一步步教你用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编写网页爬虫脚本并实现APScheduler调度
Jul 28 Python
用Python实现换行符转换的脚本的教程
Apr 16 Python
详解Python3.6的py文件打包生成exe
Jul 13 Python
实例介绍Python中整型
Feb 11 Python
python实现函数极小值
Jul 10 Python
linux环境下Django的安装配置详解
Jul 22 Python
scikit-learn线性回归,多元回归,多项式回归的实现
Aug 29 Python
python中前缀运算符 *和 **的用法示例详解
May 28 Python
python爬虫使用正则爬取网站的实现
Aug 03 Python
如何利用python生成MD5并去重
Dec 07 Python
详解基于Facecognition+Opencv快速搭建人脸识别及跟踪应用
Jan 21 Python
Python自动操作神器PyAutoGUI的使用教程
Jun 16 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
历史证明,懒惰才是推动科学发展技术进步的动力
2021/03/02 无线电
自己前几天写的无限分类类
2007/02/14 PHP
php 删除数组元素
2009/01/16 PHP
PHP 服务器配置(使用Apache及IIS两种方法)
2009/06/01 PHP
php读取xml实例代码
2010/01/28 PHP
php中时间轴开发(刚刚、5分钟前、昨天10:23等)
2011/10/03 PHP
PHP实现断点续传乱序合并文件的方法
2018/09/06 PHP
PHP中用Trait封装单例模式的实现
2019/12/18 PHP
鼠标移动到一张图片时变为另一张图片
2006/12/05 Javascript
JavaScript使用cookie
2007/02/02 Javascript
删除条目时弹出的确认对话框
2014/06/05 Javascript
JavaScript基础篇(6)之函数表达式闭包
2015/12/11 Javascript
node.js入门实例helloworld详解
2015/12/23 Javascript
基于JavaScript实现快速转换文本语言(繁体中文和简体中文)
2016/03/07 Javascript
js + css实现标签内容切换功能(实例讲解)
2017/10/09 Javascript
如何使用 vue + d3 画一棵树
2018/12/03 Javascript
JS插入排序简单理解与实现方法分析
2019/11/25 Javascript
html-webpack-plugin修改页面的title的方法
2020/06/18 Javascript
从零开始学Python第八周:详解网络编程基础(socket)
2016/12/14 Python
Python基于回溯法子集树模板解决数字组合问题实例
2017/09/02 Python
Python 实现两个列表里元素对应相乘的方法
2018/11/14 Python
对python中的try、except、finally 执行顺序详解
2019/02/18 Python
Django Python 获取请求头信息Content-Range的方法
2019/08/06 Python
python定时任务 sched模块用法实例
2019/11/04 Python
python使用rsa非对称加密过程解析
2019/12/28 Python
css3隔行变换色实现示例
2014/02/19 HTML / CSS
HTML5拍照和摄像机功能实战详解
2019/01/24 HTML / CSS
canvas学习总结三之绘制路径-线段
2019/01/31 HTML / CSS
编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的
2015/07/23 面试题
星空联盟C# .net笔试题
2014/12/05 面试题
实习医生自我评价
2013/09/22 职场文书
财务经理岗位职责
2013/11/09 职场文书
教师产假请假条范文
2014/04/10 职场文书
学雷锋倡议书
2015/01/19 职场文书
客户答谢会致辞
2015/07/30 职场文书
基于Redis的List实现特价商品列表功能
2021/08/30 Redis