使用python的scrapy来编写一个爬虫

栏目: 编程工具 · 发布时间: 5年前

内容简介:本文将介绍我是如何在python爬虫里面一步一步踩坑,然后慢慢走出来的,期间碰到的所有问题我都会详细说明,让大家以后碰到这些问题时能够快速确定问题的来源,后面的代码只是贴出了核心代码,更详细的代码暂时没有贴出来。首先我是想爬某个网站上面的所有文章内容,但是由于之前没有做过爬虫(也不知道到底那个语言最方便),所以这里想到了是用python来做一个爬虫(毕竟人家的名字都带有爬虫的含义:smile:),我这边是打算先将所有从网站上爬下来的数据放到这些东西可以去找相应的教程安装,我这里只有ElasticSearch

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

流程一览

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

使用python的scrapy来编写一个爬虫

环境需求

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

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

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

  1. tomd :将html转换成markdown
pip3 install tomd
复制代码
  1. redis :需要python的 redis 插件
pip3 install redis
复制代码
  1. scrapy :框架安装(坑)
    1. 首先我是像上面一样执行了
    pip3 install scrapy
    复制代码
    1. 然后发现缺少 gcc 组件 error: command 'gcc' failed with exit status 1
      使用python的scrapy来编写一个爬虫
    2. 然后我就找啊找,找啊找,最后终于找到了正确的解决方法(期间试了很多错误答案:sob:)。最终的解决办法就是使用 yum 来安装 python34-devel , 这个 python34-devel 根据你自己的python版本来,可能是python-devel,是多少版本就将中间的34改成你的版本, 我的是3.4.6
    yum install python34-devel
    复制代码
    1. 安装完成过后使用命令 scrapy 来试试吧。
      使用python的scrapy来编写一个爬虫

第二步,使用scrapy来创建你的项目

  1. 输入命令 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$ 
复制代码
  1. 使用genspider来生成一个基础的spider,使用命令 scrapy genspider demo juejin.im , 后面这个网址是你要爬的网站,我们先爬自己家的:joy:
liaochengdeMacBook-Pro:scrapy liaocheng$ scrapy genspider demo juejin.im
Created spider 'demo' using template 'basic'
liaochengdeMacBook-Pro:scrapy liaocheng$ 
复制代码
  1. 查看生成的目录结构
    使用python的scrapy来编写一个爬虫

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

  1. 查看生成的的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

复制代码
  1. 可以使用第二种方式,将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

复制代码
  1. 编写 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()
复制代码
  1. 编写 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)
复制代码
  1. 编写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
复制代码

第四步,运行代码查看效果

  1. 使用 scrapy list 查看本地的所有爬虫
liaochengdeMacBook-Pro:scrapyDemo liaocheng$ scrapy list
demo
liaochengdeMacBook-Pro:scrapyDemo liaocheng$ 
复制代码
  1. 使用 scrapy crawl demo 来运行爬虫
scrapy crawl demo
复制代码
  1. 到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": {}
      }
    ]
 }
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

知识的边界

知识的边界

[美] 戴维·温伯格 / 胡泳、高美 / 山西人民出版社 / 2014-12-1 / 42.00元

大数据时代反思知识 因为事实不再是事实,专家随处可见 所有确定性都被连根拔起,话题再无边界,没有人对任何事情能达成一致。 在互联网的引领下,知识现在已经具有了社交性,流动且开放。温伯格向我们展示了这些特点如何可以为我们所用。 ——马克•贝尼奥夫(云计算之父,著有《云攻略》) 这本富有洞见的著作,奠定了温伯格作为数字时代最重要的思想家之一的地位。如果你想要理解信息洪流涌......一起来看看 《知识的边界》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具