入门案例

http://tech.163.com/special/gd2016

学习目标

  • 创建一个Scrapy项目
  • 定义提取的结构化数据(Item)
  • 编写爬取网站的Spider,并提取结构化数据(Item)
  • 编写Item Pipeline来存储提取到的Item

    制作Scrapy爬虫,一共需要5步

    1. 新建项目(scrapy startproject xxx):新建一个新的爬虫项目
    2. 生成spider文件(scrapy genspider xxxx xxx.com):新建一个爬虫文件
    3. 明确目标(编写item.py文件):明确你想抓取的目标(定义需要抓取数据的字段)
    4. 制作爬虫(spiders/xxspider.py):制作爬虫开始爬取网页
    5. 存储内容(pipeline.py):设计管道存储爬取的内容

      一. 新建项目(scrapy startproject)

  • 在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的目录中,执行下列命令:

scrapy startproject News
image.png

  • 其中,News为项目名称(根据自己的需要进行自定义),执行完成后,可以看到scrapy在目录中创建了一个News的文件夹,目录结构如下所示:

└── News
├── News
│ ├── init.py
│ ├── items.py # 明确你想抓取的目标(定义需要抓取数据的字段)
│ ├── middlewares.py # 中间件文件
│ ├── pipelines.py # 管道文件
│ ├── settings.py
│ └── spiders # 爬虫程序就定义在这个文件夹下
│ └── init.py
└── scrapy.cfg # 项目的配置文件,由Scrapy自动创建(不需要我们去修改)

  • 创建爬虫文件

进入到News/News/spiders目录下,执行以下命令,创建爬虫文件:
scrapy genspider news "163.com",命令执行完成后,会在spiders目录下生成news.py文件。
其中 news为爬虫的名称,后续启动爬虫,就是根据爬虫的名称要启动(名字要唯一,不能够重复);"163.com"为要爬取的域范围,如果在爬取的过程中提取到不属于该域的URL,则scrapy不会对该URL进行爬取。

二. 明确目标(News/item.py)

我们打算抓取:163.com网站里的科技新闻信息,如新闻名称、新闻链接、新闻摘要、新闻来源、发布时间。

  1. 打开News目录下的item.py文件
  2. 在Item中定义结构化数据字段,用来保存爬取到的数据。Item有点像Python中的dict,但是提供了一些额外的保护,减少错误。
  3. 可以通过继承一个scrapy.Item类,并且定义类型为scrapy.Filed的类属性来定义一个Item。
  4. 接下来,创建一个NewsItem类,继承scrapy.Item,并构建item模型。s

    1. 打开items.py文件,定义scrapy.Field属性,存储要抓取的字段

    ```python import scrapy

定义要爬取的字段信息

class NewsItem(scrapy.Item):

  1. # define the fields for your item here like:
  2. # name = scrapy.Field()
  3. news_name = scrapy.Field()
  4. news_link = scrapy.Field()
  5. news_brief = scrapy.Field()
  6. news_source = scrapy.Field()
  7. news_time = scrapy.Field()
  1. <a name="W1WIo"></a>
  2. ## 三. 制作爬虫(spiders/news.py)
  3. **爬虫功能要分两步:**
  4. <a name="SjOIN"></a>
  5. ### 1. 爬数据
  6. - 打开spiders/news.py文件,可以看出,该文件中已经默认添加了一下代码:
  7. ```python
  8. import scrapy
  9. class NewsSpider(scrapy.Spider):
  10. name = 'news'
  11. allowed_domains = ['163.com']
  12. start_urls = ['http://163.com']
  13. def parse(self, response):
  14. pass

其实,news.py这个文件也可以由我们自行创建,并编写上面的代码,只不过通过命令创建可以免去我们写固定代码的麻烦。

要建立一个Spider,必须继承scrapy.Spider类,并且确定三个强制的属性和一个方法:

  • name = “”:这个属性是爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
  • allow_domains = []:是搜索的域名范围,也是爬虫的约束范围,规定爬虫只爬取这个域名下的网页,不在该域名下的URL会被忽略。
  • start_urls = []:爬取的URL列表。爬虫是从这里开始抓取数据,所以,第一次下载数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
  • parse(self, response):解析方法,每个初始URL完成下载后,该方法将会被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一的参数,主要作用如下:
    1. - 负责解析返回的网页源代码(response.body),提取结构化数据(生成Item
    2. - 生成需要下一页的URL请求(构建Request请求对象)

将start_urls的值修改为需要爬取的第一个URL:
start_urls = ["http://tech.163.com/special/gd2016"]
修改parse()方法:

  1. def parse(self, response):
  2. # 将提取的源代码写入到文件中
  3. with open("news.html", "w", encoding="utf-8") as fw:
  4. fw.write(response.body)

执行爬虫程序,在News目录下执行以下命令:
scrapy crawl news
注意:一个scrapy爬虫项目中,可以存在多个爬虫。各个爬虫执行时,是按照name属性进行区分。
执行完成之后,如果打印的日志出现[scrapy] INFO:Spider closed(finished),代表爬虫程序执行完成。之后当前文件夹中就出现了一个news.html文件,里面就是我们刚刚要爬取的网页的全部源代码信息。

2. 提取数据

将网页源代码抓取下来之后,接下来就是提取数据的过程了。

2.1 导入NewsItem类(在News/item.py中定义的类)

from News.items import NewsItem

2.2 然后将从网页中提取的数据封装到NewsItem对象中,保存每个商品的属性值:

  1. import scrapy
  2. from bs4 import BeautifulSoup
  3. from News.items import NewsItem
  4. class NewsSpider(scrapy.Spider):
  5. name = 'news'
  6. allowed_domains = ['163.com']
  7. # 起始URL地址列表
  8. offset = 1
  9. base_url = 'http://tech.163.com/special/gd2016'
  10. start_urls = [base_url]
  11. def parse(self, response):
  12. # 获取相应的HTML源代码
  13. html = response.body.decode('utf-8')
  14. print(type(html))
  15. # 使用BeautifulSoup对HTML进行解析
  16. soup = BeautifulSoup(html, 'lxml')
  17. # 获取ul标签下的所有li标签
  18. lis = soup.select('#news-flow-content li')
  19. # 遍历所有的li标签
  20. for li in list(lis):
  21. # 获取新闻标题
  22. news_name = list(li.select('div.titleBar h3 a'))[0].get_text()
  23. # 获取新闻链接
  24. news_link = list(li.select('div.titleBar h3 a'))[0]
  25. news_link = news_link.get('href')
  26. # 获取新闻摘要信息
  27. news_brief = list(li.select('div.newsDigest p'))[0].get_text().replace('\n', '').replace(' ', '')
  28. # 获取新闻来源
  29. news_source = list(li.select('div.newsBottom p.sourceDate span'))[0].get_text()
  30. # 获取时间
  31. news_time = list(li.select('div.newsBottom p.sourceDate'))[0].get_text().replace(news_source, '')
  32. # 构建Item对象
  33. item = NewsItem()
  34. item['news_name'] = news_name
  35. item['news_link'] = news_link
  36. item['news_brief'] = news_brief
  37. item['news_source'] = news_source
  38. item['news_time'] = news_time
  39. # 将item对象返回给引擎,由引擎再交给管道组件
  40. yield item
  • 我们暂时先不处理管道,后面再进行介绍

    3. 保存数据

    scrapy保存信息的最简单方法主要有四种。-o 输出指定格式的文件,命令如下: ```python

    输出JSON格式,默认为Unicode编码

    scrapy crawl beibus -o teachers.json

JSON lines格式

scrapy crawl beibus -o teachers.jsonl

csv 逗号表达式,可以用Excel打开

scrapy crawl beibus -o teachers.csv

xml 格式

scrapy crawl -o teachers.xml ```

补充知识点(标签文本和属性值的获取)

1 获取文本值

在选择器后加上::text关键字即可。
response.css('title::text')

2 获取属性值

在选择器后加上attr(属性名)即可。
response.css('base::attr(href)').get()
response.css('base').attrib['href']

3 get()和getall()方法

要实际提取文本数据,必须调用选择器.get()或.getall()方法。
get()方法总是返回单个元素,如果有多个元素被匹配,则只会返回匹配列表中的第一个元素。如果没有匹配到元素,则会返回None。
当没有元素匹配到,又不想让get()方法返回None时,可以通过get()方法的default参数,设置默认返回的值。如下所示:
response.css('title::text').get(default=“not-found”)

getall()方法返回包含所有结果的列表。