前言 最近需要做一个通过神经网络(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"