scrapy爬取豆瓣电影评论


因为毕设需要,所以决定爬取豆瓣评论进行分析,

使用框架:scrapy,多线程爬取,效率高,尽管python多线程是个渣渣。易于拓展,

1.创建scrapy项目,

scrapy  startproject  douban_crawler

目录如下图

douabn_crawler 是项目目录,spiders是爬虫文件的目录,下一步执行创建爬虫命令后,爬虫文件默认存放在 spiders目录下 

2.创建爬虫

在项目目录下执行以下命令

scrapy genspider douban  douban.com

douban 是爬虫名,douban.com 指的是爬虫爬取的起始位置,存放位置如上所示,

运行过程

3.

settings

一些对爬虫的设置,

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


import scrapy

BOT_NAME = 'douban_crawler'          #  项目名

SPIDER_MODULES = ['douban_crawler.spiders']     
NEWSPIDER_MODULE = 'douban_crawler.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
# USER_AGENT = 'douban_crawler (+http://www.yourdomain.com)'
# USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.63 Safari/537.31"

# Obey robots.txt rules
# ROBOTSTXT_OBEY = True              # 用于遵守 通用搜索引擎协议的, True为遵守

# Configure maximum concurrent requests performed by Scrapy (default: 16) CONCURRENT_REQUESTS = 1                      # 请求的并发数,默认为16个,也可以再往上加,得看运行的机器配置,

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 1            #  延时下载设置,发送请求后,延时 1s 下载数据  ,用以限制爬取速度,减轻服务器压力
# The download delay setting will honor only one of:
# CONCURRENT_REQUESTS_PER_DOMAIN = 16         #  禁用这个和下面那个,让 并发数完全由 CURRENT_REQUESTS 来控制  
# CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
COOKIES_ENABLED = False                   # 是否启用cookie,不设置的话默认启用,自动处理,不启用的话效率会更快一点点

RETRY_ENABLED = False                        #  请求失败的话是否重试,如果少上一些数据可以接受的话,关闭重试,可以提升一点爬取速度
# Disable Telnet Console (enabled by default)
# TELNETCONSOLE_ENABLED = False                          

# Override the default request headers:
# DEFAULT_REQUEST_HEADERS = {                                             #  默认的请求头,重写即可,用这个请求头爬取豆瓣无法得到数据,但是其他个人网站可以拿到      
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
# }

# Enable or disable spider middlewares                           
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
# SPIDER_MIDDLEWARES = {                                                          #  介于 spider 和 engine 之间,过滤掉无效的 request ,response
#    'douban_crawler.middlewares.DoubanCrawlerSpiderMiddleware': 543,
# }

# Enable or disable downloader middlewares                       
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {                                                         #  介于 downloader 和 engine 之间, 对request进行处理,比如加上请求头,和代理ip
    'douban_crawler.middlewares.RandomUserAgentMiddleware': 400,            #   处理response,比如利用 selenium + phantomjs 重构response,执行js代码
    # 'douban_crawler.middlewares.SeleniumMiddleware': 500,                          
    'douban_crawler.middlewares.RandomProxyMiddleware': 125,
    # 'scrapy.downloadermiddleware.httpproxy.HttpProxyMiddleware':543,
}
# HTTPPROXY_ENNABLED = True                                   
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
# EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
# }

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    # 'douban_crawler.pipelines.FilePipeline': 300,
    # 'douban_crawler.pipelines.DoubanCrawlerPipeline': 300,
    # 'douban_crawler.pipelines.CsvPipeline': 300,
    'douban_crawler.pipelines.TxtFilePipeline': 300,                        # 启用的管道文件,

}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
AUTOTHROTTLE_ENABLED = True
# The initial download delay
# AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
# AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
# AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
HTTPCACHE_ENABLED = True
# HTTPCACHE_EXPIRATION_SECS = 0
# HTTPCACHE_DIR = 'httpcache'
# HTTPCACHE_IGNORE_HTTP_CODES = []
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
# FEED_EXPORT_ENCODING = 'gbk'
FEED_EXPORT_ENCODING = 'utf-8'              # 如果存储到本地的json文件或者txt文件,这个参数用以文件编码格式,


LOG_FILE = 'geturls.log'           #  日志文件, 
LOG_LEVEL = 'INFO'                 #  写进日志文件的信息级别,由高到低分别为:  ERROR、WARNING、INFO、DEBUG 

USER_AGENTS = [
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.63 Safari/537.31",
    "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36Edge/13.10586",
    "User-Agent:Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
    "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
    "User-Agent:Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
]

item

这是我们需要爬取数据的定义,类似于一种字典的格式,我们需要爬取的是电影名字,简介,类别,评分。定义如下,

 1 from scrapy import Field,Item
 2 
 3 
 4 class DoubanCrawlerItem(Item):               #编写的每一个 Item  类 都需要继承 scrapy.Item
 5     # define the fields for your item here like:
 6     # name = scrapy.Field()
 7     name = Field()                          #  Field()  不要忘了加括号,想文字,图片,视频等格式都写Field(),
 8     desc = Field()
 9     cate = Field()                          
10     grade = Field()

middwares

这是拓展的中间件,比如随机请求头,代理ip,selenium点击动态网页等。

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

import random

import requests
from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver import ActionChains
import time
from .settings import USER_AGENTS


class RandomUserAgentMiddleware(object):              
    def __init__(self):
        self.agents = USER_AGENTS

    def process_request(self, request, spider):             # 设置随机请求头,必写方法
        # 随机获取请求头b
        request.headers.setdefault('User-Agent', random.choice(self.agents))



class SeleniumMiddleware():                           #  selenium + phantomJs 模拟点击  使用版本 selenium == 3.141.0  phantomJs:2016-01-23: Version 2.1.0
    def __init__(self):                    # 这里我也感到奇怪,不是不支持了吗?,但我确实用了,而且运行成功了
        self.browser = webdriver.PhantomJS(executable_path=r"C:\Users\LFC25\AppData\Local\Programs\Python\Python38-32\phantomjs\bin\phantomjs.exe")
        # self.browser.set_window_size(1400,700)


    def process_request(self, request, spider):          #  必写方法,用以重构返回 response对象,点击后 再进行解析
self.browser.get(request.url) # 加载url eles = self.browser.find_elements_by_xpath('//a[@class="unfold"]') # 找到需要点击的 按钮处,列表形式循环点击 for i in range(0,len(eles)): ActionChains(self.browser).move_to_element(eles[i]).click(eles[i]).perform() time.sleep(3) # 点击后等待反应时间
return HtmlResponse(url=request.url, # 返回点击好了的response body=self.browser.page_source, request=request, # 最好根据网页的具体编码而定 encoding='utf-8', status=200) def __del__(self): self.browser.close() class RandomProxyMiddleware(object): # 设置随机ip,使用插件为 ProxyPool-master 原理页面:https://cuiqingcai.com/7048.html def process_request(self,request,spider): # github连接:https://github.com/Python3WebSpider/ProxyPool PROXY_POOL_URL = 'http://localhost:5555/random' # 本机pycharm 打开这个项目,然后一直运行即可, response = requests.get(PROXY_POOL_URL) if response.status_code == 200: request.meta['proxy'] = response.text

pipeline

这是数据过滤,加工,存储的管道文件,数据爬取到本机并解析后,通过这个文件初步处理后存储。

import json
import pymysql
import csv

class DoubanCrawlerPipeline(object):
    def __init__(self):
        # 连接数据库
        self.connect = pymysql.connect(
            host='127.0.0.1',  # 数据库地址
            port=3306,  # 数据库端口
            db='graduation',  # 数据库名
            user='root',  # 数据库用户名
            passwd='41623218',  # 数据库密码
            charset='utf8',  # 编码方式         # 必须写 utf8  不能是 utf-8
            use_unicode=False)

        # 通过cursor执行增删查改
        self.cursor = self.connect.cursor()

    def process_item(self, item, spider):
        sql = ('insert into comment values (%s,%s,%s,%s)')          #  无论插入什么数据,都用  %s 进行占位
        lis = (item['uname'], item['mname'], item['comment'], item['grade'])     
        try:
            self.cursor.execute(sql, lis)                           
            self.connect.commit()                                #  执行语句后,进行提交,不然实际上并未插入数据
        except Exception as e: 
            print("insert err:", e)
        finally:
            return item                                   

    def close_spider(self, spider):
        self.connect.close()



class FilePipeline(object):                             # 写入json文件
    def __init__(self):
        self.filename = open("url.json", "w",encoding='utf-8')

    def process_item(self, item, spider):                     
        text = json.dumps(dict(item),ensure_ascii=False) + "\n"            #  用 json.dumps  转换 python的字典格式,ensure_ascii 需要设置为False,防止使用ascii 进行中文编码
        self.filename.write(text)
        return item

    def close_spider(self, spider):
        self.filename.close()

爬虫文件

1.由命令 scrapy genspider douban douban.com生成

class MovieSpider(Spider):             # 编写的爬虫文件,继承Spider
name = 'movie'                  # 爬虫名
allowed_domains = ['movie.douban.com']      # 爬虫作用域
start_urls = ['https://movie.douban.com/top250'] # 起始爬取的url地址,一般是列表或者元组

def parse(self, response): # 直接写对 response 解析的方法即可,
item = DoubanCrawlerItem()
selector = Selector(response)
# trs = selector.xpath('//tr[@class="odd"]')
name = selector.xpath('//h1//span[1]/text()').extract[0]
cate = selector.xpath('//div[@class="indent clearfix"]//span[@property="v:genre"]/text()').extract()[0]
desc = selector.xpath('//span[@property="v:summary"]/text()').extract()
grade = selector.xpath('//strong/text()').extract()
item["grade"] = int(float("".join(grade).strip())*10)
    
    url = re.sub('\d+', str(page), response.url)      
     yield scrapy.Request(url, callback = self.parse)           # 生成 url,发到队列,  这句瞎写,用来表示 继承 Spider 类的爬虫深度爬取的方法就是在当前页面解析得到url地址后,
        yield item                                           #    yield生成到队列中即可

生成CrawlSpider深度爬虫

命令

scrapy genspider -t crawl Comment  'douban.com'

爬虫文件:




class CommentSpider(CrawlSpider):         #  继承CrawlSpider类,表示这是一个不一般的爬虫
    name = 'comment'
    allowed_domains = ['www.douban.com']                   
    start_urls = ['https://www.douban.com/people/3540441/reviews',
                  'https://www.douban.com/people/conanemily/reviews',
                  'https://www.douban.com/people/chenchangxing/reviews',
                  'https://www.douban.com/people/bluemud/reviews',
                  'https://www.douban.com/people/qijiuzhiyue/reviews',]

    rules = (                                     #  rules 表示 页面提取url的规则,内部主要也是用的 re 模块,
        Rule(LinkExtractor(allow=r'reviews',restrict_xpaths=('//div[@class="paginator"]')), callback='parse_item', follow=True),
                              #   LinkExtractor( allow  表示提取url的正则规则,不需要写全,但需要是特征部分,  restrict_xpath 表示限制提取的区域,)
    )                           #   callback  表示调用的解析函数,  follow 表示是否跟进,

    def parse_item(self, response):              #   解析函数名不能为 parse 
        item = CommentItem()
        # selector = Selector(response)
        divs = response.xpath('//div[@class="main review-item"]')
        for div in divs:
            item['uname'] = div.xpath('.//header//a/text()').extract()[-1]
            item['mname'] = div.xpath('.//h2//a/text()').extract()[0]
            comment = div.xpath('.//div[@class="review-content clearfix"]')
            info = comment.xpath('string(.)').extract()
            data = "".join(info)
            item['comment'] = data.strip()
            # item['comement'] = re.findall(r'\d+', comement)
            grade = div.xpath('.//header//span[1]').extract()[0]
            item['grade'] = int(re.findall(r'\d+',grade)[0])
            yield item

其他一些点

1.scrapy内部有指纹过滤器,会对每个url生成指纹并记录,一旦当前请求的url存在指纹库中,就会跳过

2.该过滤器适合数据量较小的情况,数据量很大时,需要重写过滤器redisdupefileter,bloom filter等

3.不同的浏览器请求头获取到的html有时候不太一样,貌似老版本 ie 的比较标准,如果是在chrome中解析的,可以多用 chrome 的User-Agent

4.一些反爬手段和应对,

User-Agent 设置随机User-Agent
refer字段,表示你页面跳转的依据 在请求中加上refer字段
cookie验证 启用cookie
js 折叠数据 selenium处理,scrapy-splash
验证码 图像识别,打码平台
要求登录后才能获取数据,且登录页面隐藏着相当多的验证数据, 爬虫登录,且准备多个账号,抓包登录数据的发送,
ip限制, 购买高质量代理,
css 计算css偏移
数据隐藏,加密,例子:知网 雇三哥们,人力点击获取