前言 最近需要做一个通过神经网络(LSTM)做情感分析的项目。第一步数据集就难住了,英文可以用 IMDB 的评论数据集,但是没有找到好用的中文数据集,就想着自己用爬虫爬一些数据。考虑了一下,决定用豆瓣的影评作为原始数据,一方面是和 IMDB 数据集类似,处理数据时可以借鉴一下,而且豆瓣影评带一个分数,可以方便标记数据,不用人工标记,但是得针对性的选一些电影的影评。
以前也写过爬虫,但是用得是最基本的 urllib 库,这次为了更好更快的爬取数据,决定学习 Scrapy 这个爬虫框架。本文开始会介绍 Scrapy 的基本的用法,然后给出爬虫代码,最后我会把我爬去的数据集预处理后分享出来。
Scrapy 使用 创建一个 Scrapy 工程 首先安装 Scrapy pip install scrapy
选择一个喜欢的目录,运行下面的命令,Scrapy 会根据模板创建一个 douban
工程。
Copy 1
scrapy startproject douban
目录结构如下:
douban/
scrapy.cfg # 部署的配置文件
douban/ # 工程的 Python 模块,在里面写代码
init .py
items.py
middlewares.py
pipelines.py
settings.py
spiders/
init .py
第一个爬虫 爬虫是一个继承自 scrapy.Spider
的类,Scrapy 通过定义的类爬取数据。文件应该保存在 spiders 文件夹里。下面是一个例子,爬取豆瓣首页内容:
Copy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
import scrapy
class DoubanIndexSpider (scrapy. Spider):
name = "DoubanIndex"
def start_requests (self ):
urls = ["https://www.douban.com" ]
for url in urls:
yield scrapy. Request(url= url, callback= self . parse)
def parse (self , response):
with open ("douban_index.html" , "wb" ) as f:
f. write(response. body)
name
是一个爬虫的名字,在同一个工程内,它应该是独一无二的。
start_requests
请求发起请求
parse
解析响应数据
运行爬虫 到工程的根目录运行下面的命令:
Copy 1
scrapy crawl DoubanIndex
第一次运行爬不了,修改 settings.py
中的 User-Agent
为:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36
成功把豆瓣首页爬下来保存到 douban_index.html
可以不写 start_requests
方法,用 start_urls
代替
交互式解析数据 使用 Scrapy shell
方法可以交互式的解析数据,类似 IPython,在学习或者分析网页时非常方便:
Copy 1
scrapy shell "https://www.douban.com/"
可以使用各种选择器对 response
中的内容进行选择
Copy 1
2
response.css( ".lnk-book" ) .get()
response.css( "div" ) .getall()
定义 Item Item 是保存爬取到的数据的容器。其使用方法和 python 字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
我们在 items.py 内定义我们需要保存的数据。
Copy 1
2
3
4
import scrapy
class DoubanIndexItem (scrapy. item):
url = scrapy. Field()
上面我们只保存 url
在代码中解析数据 Copy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
import scrapy
from douban.items import DoubanIndexItem
class DoubanIndexSpider (scrapy. Spider):
name = "DoubanIndex"
start_urls = ["https://douban.com" ]
def parse (self , response):
# 解析豆瓣首页的 nav-bar 的link url
for li in response. css("div#anony-nav div.anony-nav-links ul li" ):
item = DoubanIndexItem()
item["url" ] = li. css("a::attr(href)" ). get()
yield item # 返回item
将输出保存在文件中。
Copy 1
scrapy crawl DoubanIndex -o nav-link.json
运行后,/nav-link.json/ 的内容如下:
[
{“url”: “https://book.douban.com ”},
{“url”: “https://movie.douban.com ”},
{“url”: “https://music.douban.com ”},
{“url”: “https://www.douban.com/group/ ”},
{“url”: “https://www.douban.com/location/ ”},
{“url”: “https://douban.fm ”},
{“url”: “https://time.douban.com/?dt_time_source=douban-web_anonymous_index_top_nav ”},
{“url”: “https://market.douban.com?utm_campaign=anonymous_top_nav&utm_source=douban&utm_medium=pc_web ”}
跟踪页面的链接 上一步已经获取了链接,下一部我们爬链接里的内容,即动态的添加链接。
Copy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding: utf-8 -*-
import scrapy
class DoubanIndexSpider (scrapy. Spider):
name = "DoubanIndex"
start_urls = ["https://www.douban.com/" ]
def parse (self , response):
# 解析豆瓣首页的 nav-bar 的link url
urls = []
for li in response. css("div#anony-nav div.anony-nav-links ul li" ):
url = li. css("a::attr(href)" ). get()
urls. append(url)
yield {
"url" : response. url
}
for url in urls:
if url is not None :
next_page = response. urljoin(url)
yield scrapy. Request(next_page, callback= self . parse)
使用 response.follow
更加方便,可以直接传入 a
标签。
Copy 1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding: utf-8 -*-
import scrapy
class DoubanIndexSpider (scrapy. Spider):
name = "DoubanIndex"
start_urls = ["https://www.douban.com/" ]
def parse (self , response):
for li in response. css("div#anony-nav div.anony-nav-links ul li" ):
a = li. css("a" )[0 ]
yield response. follow(a, callback= self . parse)
上面这些内容足够我完成爬虫任务了,使用框架就是这么简单。
爬取豆瓣电影评论 明确目标 我需要爬豆瓣电影的评论,评论附带一个五星的评分。把 12 星看做负面情绪,3星看做中性,
45 星看做正面情绪。
我们先爬取所有电影的 ID, 然后通过分层加随机抽样的方法选出 100 部电影爬取该电影的评论。每一部的每一种情绪分别爬取 100 条。
保存方式:使用文本文件保存,三种情绪分为三个文件夹:每个文件夹一个 txt 文件保存一条评论,文件名为 id_rate.txt,id 为独一无二的数字。
Pipeline 上面要将爬取的评论放到不同的文件里,可以用 scrapy 的 pipeline 机制,scrapy 将爬取的 Item 送到 pipeline。在 pipelines.py 文件里定义 pipeline。
Copy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import os
class DoubanMovieCommentPipeline (object ):
index_pos = 1
index_nosup = 1
index_neg = 1
def create_dir (self , path):
if not os. path. exists(path):
os. makedirs(path)
def process_item (self , item, spider):
if item["short" ] is None or len (item["short" ]) <= 10 :
return item
path = "doubancomment/" + item["type" ]
if item["type" ] == "pos" :
filepath = path + "/" + str (self . index_pos) + "_" + item["rate" ] + ".txt"
self . index_pos += 1
elif item["type" ] == "nosup" :
filepath = path + "/" + str (self . index_nosup) + "_" + item["rate" ] + ".txt"
self . index_nosup += 1
else :
filepath = path + "/" + str (self . index_neg) + "_" + item["rate" ] + ".txt"
self . index_neg += 1
self . create_dir(path)
with open (filepath, "wb" ) as f:
f. write(item["short" ]. encode("utf-8" ))
return item
process_item
负责处理传过来的 item。在 settings.py
里定义 pipeline
顺序。
Copy 1
ITEM_PIPELINES = {"douban.pipelines.DoubanMovieCommentPipeline" : 300 }
可以在里面定义多个 pipeline,item 会按照定义的顺序传到每个 pipeline 处理。
一些设置 在 settings.py
设值一些选项
Copy 1
2
3
4
5
6
7
8
9
10
11
# 不遵守 robots.txt
ROBOTSTXT_OBEY = False
# 每一次下载页面时延迟,防止被封
DOWNLOAD_DELAY = 1.5
# 不使用cookie
COOKIES_ENABLED = False
# 设值 user-agent
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36"