python scrapy 框架原理
一步步教你用python的scrapy编写一个爬虫介绍
本文将介绍我是如何在python爬虫里面一步一步踩坑,然后慢慢走出来的,期间碰到的所有问题我都会详细说明,让大家以后碰到这些问题时能够快速确定问题的来源,后面的代码只是贴出了核心代码,更详细的代码暂时没有贴出来。
流程一览
首先我是想爬某个网站上面的所有文章内容,但是由于之前没有做过爬虫(也不知道到底那个语言最方便),所以这里想到了是用python来做一个爬虫(毕竟人家的名字都带有爬虫的含义),我这边是打算先将所有从网站上爬下来的数据放到elasticsearch里面, 选择elasticsearch的原因是速度快,里面分词插件,倒排索引,需要数据的时候查询效率会非常好(毕竟爬的东西比较多),然后我会将所有的数据在elasticsearch的老婆kibana里面将数据进行可视化出来,并且分析这些文章内容,可以先看一下预期可视化的效果(上图了),这个效果图是kibana6.4系统给予的帮助效果图(就是说你可以弄成这样,我也想弄成这样)。后面我会发一个dockerfile上来(现在还没弄)。
环境需求
- 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
3、然后我就找啊找,找啊找,最后终于找到了正确的解决方法(期间试了很多错误答案)。最终的解决办法就是使用yum来安装python34-devel, 这个python34-devel根据你自己的python版本来,可能是python-devel,是多少版本就将中间的34改成你的版本, 我的是3.4.6
|
yum install python34 - devel |
4、安装完成过后使用命令 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$ |
查看生成的目录结构
第三步,打开项目,开始编码
查看生成的的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 > li.view-container > main > li > li.main-area.article-area.shadow > article > h1::text" ).extract_first() # 内容 parameter = response.css( "#juejin > li.view-container > main > li > li.main-area.article-area.shadow > article > li.article-content" ).extract_first() article[ 'content' ] = self .parsetomarkdown(parameter) # 作者 article[ 'author' ] = response.css( "#juejin > li.view-container > main > li > li.main-area.article-area.shadow > article > li:nth-child(6) > meta:nth-child(1)::attr(content)" ).extract_first() # 创建时间 createtime = response.css( "#juejin > li.view-container > main > li > li.main-area.article-area.shadow > article > li.author-info-block > li > li > time::text" ).extract_first() createtime = str (createtime).replace( "年" , "-" ).replace( "月" , "-" ).replace( "日" ,"") article[ 'createtime' ] = createtime # 阅读量 article[ 'readnum' ] = int ( str (response.css( "#juejin > li.view-container > main > li > li.main-area.article-area.shadow > article > li.author-info-block > li > li > span::text" ).extract_first()).split( " " )[ 1 ]) # 点赞数 article[ 'badge' ] = response.css( "#juejin > li.view-container > main > li > li.article-suspended-panel.article-suspended-panel > li.like-btn.panel-btn.like-adjust.with-badge::attr(badge)" ).extract_first() # 评论数 article[ 'commentnum' ] = response.css( "#juejin > li.view-container > main > li > li.article-suspended-panel.article-suspended-panel > li.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 = {
|