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偏移 |
| 数据隐藏,加密,例子:知网 | 雇三哥们,人力点击获取 |