爬虫必备—Scrapy

Posted 三百六十行,行行转IT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫必备—Scrapy相关的知识,希望对你有一定的参考价值。

一、Scrapy简介

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下:

jackboy。。。

Scrapy主要包括了以下组件:

  • 引擎(Scrapy)

  用来处理整个系统的数据流处理, 触发事务(框架核心)

  调度器(Scheduler)
  用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

  • 下载器(Downloader)

  用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)

  • 爬虫(Spiders)

  爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面

  • 项目管道(Pipeline)

  负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

  • 下载器中间件(Downloader Middlewares)

  位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。

  • 爬虫中间件(Spider Middlewares)

  介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。

  • 调度中间件(Scheduler Middewares)

  介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

 

Scrapy运行流程大概如下:

  1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
  2. 引擎把URL封装成一个请求(Request)传给下载器
  3. 下载器把资源下载下来,并封装成应答包(Response)
  4. 爬虫解析Response
  5. 解析出实体(Item),则交给实体管道进行进一步的处理
  6. 解析出的是链接(URL),则把URL交给调度器等待抓取

二、安装

 1 Linux
 2       pip3 install scrapy
 3  
 4  
 5 Windows
 6       a. pip3 install wheel
 7       b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
 8       c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
 9       d. pip3 install scrapy
10       e. 下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/

三、基本使用

1. 基本命令

1. scrapy startproject 项目名称
   - 在当前目录中创建中创建一个项目文件(类似于Django)
 
2. scrapy genspider [-t template] <name> <domain>
   - 创建爬虫应用
   如:
      scrapy gensipider -t basic oldboy oldboy.com
      scrapy gensipider -t xmlfeed autohome autohome.com.cn
   PS:
      查看所有命令:scrapy gensipider -l
      查看模板命令:scrapy gensipider -d 模板名称
 
3. scrapy list
   - 展示爬虫应用列表
 
4. scrapy crawl 爬虫应用名称
   - 运行单独爬虫应用

2. 项目结构及爬虫应用简介

2.1. 目录结构
project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           爬虫1.py
           爬虫2.py
           爬虫3.py
2.2. 文件说明
  • scrapy.cfg  项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
  • items.py    设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines    数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等
  • spiders      爬虫目录,如:创建文件,编写爬虫规则

注意:一般创建爬虫文件时,以网站域名命名

2.3. 示例
 1 import scrapy
 2  
 3 class XiaoHuarSpider(scrapy.spiders.Spider):
 4     name = "xiaohuar"                            # 爬虫名称 *****
 5     allowed_domains = ["xiaohuar.com"]  # 允许的域名
 6     start_urls = [
 7         "http://www.xiaohuar.com/hua/",   # URL
 8     ]
 9  
10     def parse(self, response):
11         # 访问起始URL并获取结果后的回调函数
爬虫1.py

若在Windows系统上若出现编码错误,解决方法如下:

1 import sys,os
2 
3 sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding=\'gb18030\')

3. 小试牛刀

 1 import scrapy
 2 from scrapy.selector import htmlXPathSelector
 3 from scrapy.http.request import Request
 4  
 5  
 6 class DigSpider(scrapy.Spider):
 7     # 爬虫应用的名称,通过此名称启动爬虫命令
 8     name = "dig"
 9  
10     # 允许的域名
11     allowed_domains = ["chouti.com"]
12  
13     # 起始URL
14     start_urls = [
15         \'http://dig.chouti.com/\',
16     ]
17  
18     has_request_set = {}
19  
20     def parse(self, response):
21         print(response.url)
22  
23         hxs = HtmlXPathSelector(response)
24         page_list = hxs.select(\'//div[@id="dig_lcpage"]//a[re:test(@href, "/all/hot/recent/\\d+")]/@href\').extract()
25         for page in page_list:
26             page_url = \'http://dig.chouti.com%s\' % page
27             key = self.md5(page_url)
28             if key in self.has_request_set:
29                 pass
30             else:
31                 self.has_request_set[key] = page_url
32                 obj = Request(url=page_url, method=\'GET\', callback=self.parse)
33                 yield obj
34  
35     @staticmethod
36     def md5(val):
37         import hashlib
38         ha = hashlib.md5()
39         ha.update(bytes(val, encoding=\'utf-8\'))
40         key = ha.hexdigest()
41         return key
爬取抽屉网页

执行此爬虫文件,则在终端进入项目目录执行如下命令:

1 scrapy crawl dig --nolog

对于上述代码重要之处在于:

  • Request是一个封装用户请求的类,在回调函数中yield该对象表示继续访问
  • HtmlXpathSelector用于结构化HTML代码并提供选择器功能

4. 选择器

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 from scrapy.selector import Selector, HtmlXPathSelector
 4 from scrapy.http import HtmlResponse
 5 html = """<!DOCTYPE html>
 6 <html>
 7     <head lang="en">
 8         <meta charset="UTF-8">
 9         <title></title>
10     </head>
11     <body>
12         <ul>
13             <li class="item-"><a id=\'i1\' href="link.html">first item</a></li>
14             <li class="item-0"><a id=\'i2\' href="llink.html">first item</a></li>
15             <li class="item-1"><a href="llink2.html">second item<span>vv</span></a></li>
16         </ul>
17         <div><a href="llink2.html">second item</a></div>
18     </body>
19 </html>
20 """
21 response = HtmlResponse(url=\'http://example.com\', body=html,encoding=\'utf-8\')
22 # hxs = HtmlXPathSelector(response)
23 # print(hxs)
24 # hxs = Selector(response=response).xpath(\'//a\')
25 # print(hxs)
26 # hxs = Selector(response=response).xpath(\'//a[2]\')
27 # print(hxs)
28 # hxs = Selector(response=response).xpath(\'//a[@id]\')
29 # print(hxs)
30 # hxs = Selector(response=response).xpath(\'//a[@id="i1"]\')
31 # print(hxs)
32 # hxs = Selector(response=response).xpath(\'//a[@href="link.html"][@id="i1"]\')
33 # print(hxs)
34 # hxs = Selector(response=response).xpath(\'//a[contains(@href, "link")]\')
35 # print(hxs)
36 # hxs = Selector(response=response).xpath(\'//a[starts-with(@href, "link")]\')
37 # print(hxs)
38 # hxs = Selector(response=response).xpath(\'//a[re:test(@id, "i\\d+")]\')
39 # print(hxs)
40 # hxs = Selector(response=response).xpath(\'//a[re:test(@id, "i\\d+")]/text()\').extract()
41 # print(hxs)
42 # hxs = Selector(response=response).xpath(\'//a[re:test(@id, "i\\d+")]/@href\').extract()
43 # print(hxs)
44 # hxs = Selector(response=response).xpath(\'/html/body/ul/li/a/@href\').extract()
45 # print(hxs)
46 # hxs = Selector(response=response).xpath(\'//body/ul/li/a/@href\').extract_first()
47 # print(hxs)
48  
49 # ul_list = Selector(response=response).xpath(\'//body/ul/li\')
50 # for item in ul_list:
51 #     v = item.xpath(\'./a/span\')
52 #     # 或
53 #     # v = item.xpath(\'a/span\')
54 #     # 或
55 #     # v = item.xpath(\'*/a/span\')
56 #     print(v)
View Code

5. 登陆抽屉实现自动点赞或取消赞

 1 # -*- coding: utf-8 -*-
 2 import scrapy
 3 import json
 4 import urllib.parse
 5 from scrapy.selector import Selector, HtmlXPathSelector
 6 from scrapy.http import Request
 7 
 8 
 9 class ChoutiSpider(scrapy.Spider):
10     name = \'chouti\'
11     allowed_domains = [\'dig.chouti.com\']
12     start_urls = [\'http://dig.chouti.com/\']
13     cookie_dict = {}
14     news_dict = {}
15     type = \'undo\'       # 取消点赞undo,点赞do
16 
17     def start_requests(self):
18         for url in self.start_urls:
19             yield Request(url, dont_filter=True, callback=self.login)
20 
21     def login(self, response):
22         print(\'login...\\n\')
23         from scrapy.http.cookies import CookieJar
24         ck_jar = CookieJar()
25         ck_jar.extract_cookies(response, response.request)  # 去响应中获取cookies
26         for k, v in ck_jar._cookies.items():
27             for i, j in v.items():
28                 for m, n in j.items():
29                     self.cookie_dict[m] = n.value
30         print(self.cookie_dict,\'\\n\')
31         post_dict = {
32             \'phone\': 123456789,
33             \'password\': \'**********\',
34             \'oneMonth\': 1,
35         }
36 
37         yield Request(
38             url=\'http://dig.chouti.com/login\',
39             method=\'POST\',
40             cookies=self.cookie_dict,
41             body=urllib.parse.urlencode(post_dict),
42             headers={\'Content-Type\': \'application/x-www-form-urlencoded; charset=UTF-8\'},
43             callback=self.get_news
44         )
45 
46     def get_news(self, response):
47         yield Request(url=\'http://dig.chouti.com/\', cookies=self.cookie_dict, callback=self.do)
48 
49     def do(self, response):
50         hxs = Selector(response)
51         link_id_list = hxs.xpath(\'//div[@class="part2"]/@share-linkid\').extract()
52         print(link_id_list)
53         undo_url = \'http://dig.chouti.com/vote/cancel/vote.do\'
54         for link_id in link_id_list:
55             if self.type == \'undo\':
56                 print(\'undo.........\\n\')
57                 post_dict = {\'linksId\': link_id}
58                 yield Request(
59                     undo_url, method=\'POST\',
60                     cookies=self.cookie_dict,
61                     body=urllib.parse.urlencode(post_dict),
62                     headers={\'Content-Type\': \'application/x-www-form-urlencoded; charset=UTF-8\'},
63                     callback=self.showret)
64             else:
65                 print(\'dododod.......\\n\')
66                 thumb_url = "http://dig.chouti.com/link/vote?linksId=%s" % (link_id,)
67                 yield Request(thumb_url, method=\'POST\', cookies=self.cookie_dict, callback=self.showret)
68         page_list = hxs.xpath(\'//a[@class="ct_pagepa"]/@href\').extract()
69         for page in page_list:
70             # http://dig.chouti.com/all/hot/recent/2
71             page_url = \'http://dig.chouti.com%s\' %(page,)
72             yield Request(page_url, method=\'GET\', callback=self.do)
73 
74 
75     def showret(self, response):
76         print(\'show...\\n\', response.text)
抽屉网站自动点赞或取消赞

注意:settings.py中设置DEPTH_LIMIT = 1来指定“递归”的层数,即只操作当前页。

6. 格式处理

上述实例只是简单的处理,所以在parse方法中直接处理。如果对于想要获取更多的数据处理,则可以利用Scrapy的items将数据格式化,然后统一交由pipelines来处理。

 1 import scrapy
 2 from scrapy.selector import HtmlXPathSelector
 3 from scrapy.http.request import Request
 4 from scrapy.http.cookies import CookieJar
 5 from scrapy import FormRequest
 6 
 7 
 8 class XiaoHuarSpider(scrapy.Spider):
 9     # 爬虫应用的名称,通过此名称启动爬虫命令
10     name = "xiaohuar"
11     # 允许的域名
12     allowed_domains = ["xiaohuar.com"]
13 
14     start_urls = [
15         "http://www.xiaohuar.com/list-1-1.html",
16     ]
17     # custom_settings = {
18     #     \'ITEM_PIPELINES\':{
19     #         \'spider1.pipelines.JsonPipeline\': 100
20     #     }
21     # }
22     has_request_set = {}
23 
24     def parse(self, response):
25         # 分析页面
26         # 找到页面中符合规则的内容(校花图片),保存
27         # 找到所有的a标签,再访问其他a标签,一层一层的搞下去
28 
29         hxs = HtmlXPathSelector(response)
30 
31         items = hxs.select(\'//div[@class="item_list infinite_scroll"]/div\')
32         for item in items:
33             src = item.select(\'.//div[@class="img"]/a/img/@src\').extract_first()
34             name = item.select(\'.//div[@class="img"]/span/text()\').extract_first()
35             school = item.select(\'.//div[@class="img"]/div[@class="btns"]/a/text()\').extract_first()
36             url = "http://www.xiaohuar.com%s" % src
37             from ..items import XiaoHuarItem
38             obj = XiaoHuarItem(name=name, school=school, url=url)
39             yield obj
40 
41         urls = hxs.select(\'//a[re:test(@href, "http://www.xiaohuar.com/list-1-\\d+.html")]/@href\')
42         for url in urls:
43             key = self.md5(url)
44             if key in self.has_request_set:
45                 pass
46             else:
47                 self.has_request_set[key] = url
48                 req = Request(url=url,method=\'GET\',callback=self.parse)
49                 yield req
50 
51     @staticmethod
52     def md5(val):
53         import hashlib
54         ha = hashlib.md5()
55         ha.update(bytes(val, encoding=\'utf-8\'))
56         key = ha.hexdigest()
57         return key
spiders/xiahuar.py
1 import scrapy
2 
3 
4 class XiaoHuarItem(scrapy.Item):
5     name = scrapy.Field()
6     school = scrapy.Field()
7     url = scrapy.Field()
items
 1 import json
 2 import os
 3 import requests
 4 
 5 
 6 class JsonPipeline(object):
 7     def __init__(self):
 8         self.file = open(\'xiaohua.txt\', \'w\')
 9 
10     def process_item(self, item, spider):
11         v = json.dumps(dict(item), ensure_ascii=False)
12         self.file.write(v)
13         self.file.write(\'\\n\')
14         self.file.flush()
15         return item
16 
17 
18 class FilePipeline(object):
19     def __init__(self):
20         if not os.path.exists(\'imgs\'):
21             os.makedirs(\'imgs\')
22 
23     def process_item(self, item, spider):
24         response = requests.get(item[\'url\'], stream=True)
25         file_name = \'%s_%s.jpg\' % (item[\'name\'], item[\'school\'])
26         with open(os.path.join(\'imgs\', file_name), mode=\'wb\') as f:
27             f.write(response.content)
28         return item
pipelines
1 ITEM_PIPELINES = {
2    \'spider1.pipelines.JsonPipeline\': 100,
3    \'spider1.pipelines.FilePipeline\': 300,
4 }
5 # 每行后面的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。
settings

对于pipeline可以做更多,如下: