一步步教你用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 13 Python
web.py在SAE中的Session问题解决方法(使用mysql存储)
Jun 24 Python
Python实现拷贝多个文件到同一目录的方法
Sep 19 Python
python 剪切移动文件的实现代码
Aug 02 Python
python 快速把超大txt文件转存为csv的实例
Oct 26 Python
Python搭建HTTP服务过程图解
Dec 14 Python
自定义实现 PyQt5 下拉复选框 ComboCheckBox的完整代码
Mar 30 Python
Python如何批量获取文件夹的大小并保存
Mar 31 Python
Python Request类源码实现方法及原理解析
Aug 17 Python
Python二元算术运算常用方法解析
Sep 15 Python
Python jieba结巴分词原理及用法解析
Nov 05 Python
python编程学习使用管道Pipe编写优化代码
Nov 20 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 中的批处理的实现
2007/06/14 PHP
php 表单数据的获取代码
2009/03/10 PHP
使用zend studio for eclipse不能激活代码提示功能的解决办法
2009/10/11 PHP
ThinkPHP访问不存在的模块跳转到404页面的方法
2014/06/19 PHP
javascript 写类方式之七
2009/07/05 Javascript
JavaScript 自动完成脚本整理(33个)
2009/10/20 Javascript
javascript,jquery闭包概念分析
2010/06/19 Javascript
addEventListener 的用法示例介绍
2014/05/07 Javascript
jquery中map函数遍历数组用法实例
2015/05/18 Javascript
纯javascript实现的小游戏《Flappy Pig》实例
2015/07/27 Javascript
jquery 动态合并单元格的实现方法
2016/08/26 Javascript
vue2里面ref的具体使用方法
2017/10/27 Javascript
JS 实现缓存算法的示例(FIFO/LRU)
2018/03/20 Javascript
vue-cli3 项目从搭建优化到docker部署的方法
2019/01/28 Javascript
详解Vue 单文件组件的三种写法
2020/02/19 Javascript
安装多版本Vue-CLI的实现方法
2020/03/24 Javascript
python抓取最新博客内容并生成Rss
2015/05/17 Python
在Python的Django框架中编写错误提示页面
2015/07/22 Python
python常见的格式化输出小结
2016/12/15 Python
简单谈谈Python中的json与pickle
2017/07/19 Python
python全栈知识点总结
2019/07/01 Python
Python学习笔记之迭代器和生成器用法实例详解
2019/08/08 Python
pygame实现俄罗斯方块游戏(AI篇2)
2019/10/29 Python
python3实现elasticsearch批量更新数据
2019/12/03 Python
python通过移动端访问查看电脑界面
2020/01/06 Python
在django中使用apscheduler 执行计划任务的实现方法
2020/02/11 Python
django ORM之values和annotate使用详解
2020/05/19 Python
Python中logging日志记录到文件及自动分割的操作代码
2020/08/05 Python
PyQt5多线程防卡死和多窗口用法的实现
2020/09/15 Python
实例讲解利用HTML5 Canvas API操作图形旋转的方法
2016/03/22 HTML / CSS
Java语言程序设计测试题改错题部分
2014/07/22 面试题
学校介绍信范文
2014/01/14 职场文书
就业表自我评价分享
2014/02/06 职场文书
法学专业大学生实习自我鉴定
2014/10/05 职场文书
2019年教师入党申请书
2019/06/27 职场文书
Redis主从配置和底层实现原理解析(实战记录)
2021/06/30 Redis