使用Scrapy框架爬取V2ex看看程序员中秋节都在讨论啥

1,340 阅读9分钟

我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛

说明

趁着活动最后一天更新蹭蹭热度,看着中秋活动的文章大部分都是前端大佬们画动画,几乎看不到后端做了什么有意思的东西,现在我就来分享自己初学的Scrapy爬取一些网站的经历路程,接下来会使用Python语言,Scrapy框架获取V2ex网站的部分评论,还有一点Google的搜索结果页,V2ex是一个真实的程序员的讨论网站,氛围非常不错,和掘金一样经常会逛,评论真实,有血有肉。会有一些反扒机制,因为时间有限不去深入研究,目标也并不是什么全站的数据。

开始

安装一些后面可能会用到的包,不要惊奇会什么会用到pillow,jieba,wordcloud,一定要看到最后~

pip3 install scrapy
pip3 install pillow
pip3 install jieba
pip3 install numpy
pip3 install wordcloud

选择一个目录初始化项目:

scrapy startproject googlespider

然后会有提示让你进入文件夹创建爬虫,我们就跟着提示来 以此执行下面两条命令

cd googlespider
scrapy genspider v2ex google.com

接下来就会生成下面的目录

2021-09-23 23-47-40 的屏幕截图.png 除了中秋.pngget_word.pyjuejin.jpg这三个文件是后面用到,其他都是上面命令生成的

前提设置

打开setting.py

ROBOTSTXT_OBEY = True 改为ROBOTSTXT_OBEY = False,这个是爬虫届的君子协议,大厂爬虫一般会遵循这个规则,我们这些小开发者一般不遵循,

修改并发设置

Scrapy框架是一个异步框架,效率那是相当的高,默认16,目前我们不研究反扒机制,只好设置成1咯,并发高了会IP被Ban掉。请求全部404,或者403。

CONCURRENT_REQUESTS = 1

打开默认的Headers

不能太明目张胆了把,直接告诉别人网站我是爬虫程序,委婉一点,掩饰一下,这是惯例了~

DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
}

打开下载延迟

为了避开反扒只好退而求其次了~,频繁就会出问题。

AUTOTHROTTLE_START_DELAY = 5

正片开始

打开生成的v2ex.py,直接上代码,注释里说清楚

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.request import Request
from googlespider.items import GooglespiderItem


class V2exSpider(scrapy.Spider):
    name = 'v2ex' #爬虫名字。启动时候会用到它
    allowed_domains = ['google.com'] #限制这个爬虫程序爬取内容的域名,在复杂的爬虫中有很多情况会爬到其他站点去
    start_urls = ['http://google.com/'] #自动生成的爬虫程序开始爬取的域名,一般不用它。
    page_data = 10  #google 的翻页页数。相当于全局变量,后面的程序改变它使用它

    #这里设置公用的headers是因为v2ex这个网站使用默认的headers请求状态直接403了,不让你访问,后面会补充这个header是如何生成的
    headers = {
        'authority': 'www.v2ex.com',
        'cache-control': 'no-cache',
        'sec-ch-ua': '"Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Linux"',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36',
        'accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
        'sec-fetch-site': 'same-origin',
        'sec-fetch-mode': 'no-cors',
        'sec-fetch-user': '?1',
        'sec-fetch-dest': 'image',
        'accept-language': 'zh-CN,zh;q=0.9',
        'cookie': 'PB3_SESSION="2|1:0|10:1632320639|11:PB3_SESSION|36:djJleDo0Ny4yNTQuODQuMjA2Ojk5NTgyNDE3|af262dbef778709e4964d0dec124e60e267c03ac8e01bf98e702cb85b7fd0698"; V2EX_LANG=zhcn; _ga=GA1.2.2017158286.1632320642; _gid=GA1.2.1291035321.1632320642; A2="2|1:0|10:1632321211|2:A2|48:M2RjYzkzMWQtOTAwZi00YTA4LWEyNTctZmQ2NTdiMmY4YmMy|b75429367c29090d8e1b29d8f3c84a7c5e723005928c6d5267e4137e6bc02e91"; V2EX_REFERRER="2|1:0|10:1632321220|13:V2EX_REFERRER|8:VG9ieTIz|fbb0b7efd4f44fcd4298c61978cf3a395fd7111e54d8185344e5e8d286dff7f4"; V2EX_TAB="2|1:0|10:1632327619|8:V2EX_TAB|8:dGVjaA==|90bd1f7b91355e424c24b6192cbef107cde9747006042d46320b41b0b991173d"; _gat=1',
        'Referer': 'https://www.v2ex.com/t/710481',
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36',
        'referer': 'https://www.v2ex.com/t/710481',
        'Origin': 'https://www.v2ex.com',
        'if-modified-since': 'Wed, 11 Aug 2021 00:32:57 GMT',
        'Accept': '*/*',
        'X-Requested-With': 'XMLHttpRequest',
        'content-length': '0',
        'content-type': 'text/plain',
        'origin': 'https://www.v2ex.com',
        'if-none-match': 'W/"55467a007c0429c0b04e98443edd5063d10f0b22"',
        'pragma': 'no-cache',
        'Content-Type': 'text/plain',
    }

    def start_requests(self):
        # 这个函数是自己的写的,也是scrapy认可的,一开始会执行这个函数
        #这个url是google的搜索页,site和inititle是google的高级搜索方法关键字,site指搜索结果只包含某个站点,intitle只搜索关键字只存与搜索结果网页的标题中
        url = "https://www.google.com/search?q=site:v2ex.com/t+intitle:%E4%B8%AD%E7%A7%8B"
        # yield这是Python的高级用法,迭代器,这里就是实现异步爬虫的关键要点,把这个url的请求解析工作交给了parse这个方法,当前函数可以继续向下执行,但是这里是没有下面的方法了,然后下面迭代器又有很多迭代器。就会出现很对的异步请求。
        yield Request(url,  callback=self.parse)

    def parse(self, response):
        # response对象是scrapy封装的对象,这里面有好多对象方法,例如下面的.selector.re就是使用正则提取网页关键内容的方法,我们提取google第一页的文章链接
        url_list = response.selector.re("https://www.v2ex.com/t/[0-9]*")
        print(url_list)
        # 如果有文章链接就解析链接,把请求文章详情的任务用异步任务交给下一个方法去完成,然后翻页,直到google的结果页再也没有文章链接了,dont_filter就是不用最后开始限制只爬google的那个allowed_domains列表里的网站了。
        if(len(url_list) > 0):
            for i in url_list:
                yield Request(url=i, callback=self.parse_detail, dont_filter=True,headers=self.headers)
            yield Request(url="https://www.google.com/search?q=site:v2ex.com/t+intitle:%E4%B8%AD%E7%A7%8B&start="+str(self.page_data), callback=self.parse)
            self.page_data += 10

    def parse_detail(self, response):
        # 这里使用xpath来解析v2ex文章内容。下面截图会补充
        xpath_str = '//*[@class="reply_content"]/text()'
        # 这里是收集爬取数据的管道,这个item管道会把数据交给下载器,下载器的代码内容下面接着说
        item = GooglespiderItem()
        word_list = response.xpath(xpath_str).getall()
        if(len(word_list)>0):
            for i in word_list:
                
                item['word'] = i
                #把爬取的内容交给管道,管道会把数据自动调度给下载器使用
                yield item

核心功能代码展示完毕,接下来讲解其中要点

Headers伪装

Headers是突破反扒的第一个最有用的工具,最简单的就是模拟正常请求的Headers,这就要利用一个网站自动生成Python可用的Headers代码。 我们先正常使用浏览器访问网站,F12->NetWork->Doc,找到唯一的网页,右键copy->Copy as CURL

2021-09-24 00-29-45 的屏幕截图.png 找到一个curl转python的在线网站,例如:tool.lu/curl

2021-09-24 00-33-41 的屏幕截图.png 这样就得到了headers。

找到评论数据

下载google插件Xpath Helper 右键随意找到一个评论检查复制xpath,然后复制到Xpath Helper就会自动出结果,真是太快了,减少了好多调试的时间成本。

2021-09-24 00-38-19 的屏幕截图.png

下载数据

格式化item,item是保存爬取到的数据的容器,主要作用是防止数据获取的时候拼写错误导致的未定义字段错误 打开items.py 在class GooglespiderItem(scrapy.Item):下加一行

word = scrapy.Field()

格式化item后要编写下载中间件,这里很简单就不细说了:

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import csv
import codecs
import re,os

class GooglespiderPipeline(object):

    def process_item(self, item, spider):
        return item

class CsvspiderPipeline(object):
    def __init__(self):
        # 构造方法打开一个csv文件,不存在就创建
        self.file = codecs.open('word.csv', 'w', encoding='utf_8_sig')

    def process_item(self, item, spider):
        fieldnames = ['word']
        w = csv.DictWriter(self.file, fieldnames=fieldnames)
        print(item) #这里是一个字典
        #写入文件
        w.writerow(item)
        return item

    def close_spider(self, spider):
        # 关闭文件
        self.file.close()

然后打开setting.py里面的设置,290是中间件优先级,复杂的项目会有很多中间件,这里只有一个随便设咯

ITEM_PIPELINES = {
   'googlespider.pipelines.CsvspiderPipeline': 290,
}

运行

在项目根目录(就是在scrapy.cfg同目录下)运行下列命令:

scrapy crawl v2ex

然后等待几分钟就爬完了就会生成一个word.csv文件在项目目录下

2021-09-24 00-56-16 的屏幕截图.png

处理数据

使用结巴分词,wordcloud,PIL等模块做一个简单的词云图片,直接上代码吧,这里没有深入研究,找了些资料都是调API,哈哈,人生苦短,我用Python~

import csv
import jieba.analyse as analyse
from PIL import Image
import matplotlib.pyplot as plt
import wordcloud
import numpy as np


all_text=''
# 读取csv,把所有的句子连成一句话
with open('./word.csv')as f:
    f_csv = csv.reader(f)
    for row in f_csv:
        print(row[0])
        all_text+=row[0]+';'

image1 = Image.open('./juejin.jpg') # 打开一个背景图作素材
tags = analyse.extract_tags(all_text, topK=100) #著名的结巴分词,挑选出最常见的100个次
text = ' '.join(tags) #把100歌词的列表改为空格间隔的一行字符串给云词这个库使用
MASK = np.array(image1) #词云的遮照为nampy数组
# 下面就是调WordCloud的api身成一张图,可以设置宽高,词的数量,背景图什么的,注意字体文件需要根据自己的系统找,我的是ubuntu的。
WC = wordcloud.WordCloud(font_path= "/usr/share/fonts/truetype/arphic/ukai.ttc",max_words=2000,mask = MASK,height= 400,width=400,background_color='white',repeat=False,mode='RGBA') #设置词云图对象属性
#生成图片
con = WC.generate(text)
# 展示图片
plt.imshow(con)
# 关闭坐标轴,plt这是一个科学计算相关的库
plt.axis("off")
plt.show()
print(tags)

背景图:

juejin.jpg 成果展示:

中秋.png

总结

所有代码在GitHub,大家觉得学到了一些东西了可以看看参考一下:koala9527/v2ex-google-scrapy-spider
结果没有什么出乎意料的词语,例如:'快乐', '中秋节', '中秋', '月饼'。也有一些小众的词语比较有意思,例如:'女朋友','学习','睡觉',哈哈。
爬虫技术难点在于反爬,最开始请求频率高了Google和V2ex请求响应都会直接404或者403,只能短时间获取一小部分数据,如果想要大量的数据,只能上动态IP,动态Headers,甚至一行行研究JS代码。
最后感谢大家欣赏,看到最后的都是大佬,欢迎大佬查漏补缺,指出错误的地方~