入门案例
http://tech.163.com/special/gd2016
学习目标
- 创建一个Scrapy项目
- 定义提取的结构化数据(Item)
- 编写爬取网站的Spider,并提取结构化数据(Item)
-
制作Scrapy爬虫,一共需要5步
在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的目录中,执行下列命令:
scrapy startproject News
- 其中,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网站里的科技新闻信息,如新闻名称、新闻链接、新闻摘要、新闻来源、发布时间。
- 打开News目录下的item.py文件
- 在Item中定义结构化数据字段,用来保存爬取到的数据。Item有点像Python中的dict,但是提供了一些额外的保护,减少错误。
- 可以通过继承一个scrapy.Item类,并且定义类型为scrapy.Filed的类属性来定义一个Item。
- 接下来,创建一个NewsItem类,继承scrapy.Item,并构建item模型。s
1. 打开items.py文件,定义scrapy.Field属性,存储要抓取的字段
```python import scrapy
定义要爬取的字段信息
class NewsItem(scrapy.Item):
# define the fields for your item here like:# name = scrapy.Field()news_name = scrapy.Field()news_link = scrapy.Field()news_brief = scrapy.Field()news_source = scrapy.Field()news_time = scrapy.Field()
<a name="W1WIo"></a>## 三. 制作爬虫(spiders/news.py)**爬虫功能要分两步:**<a name="SjOIN"></a>### 1. 爬数据- 打开spiders/news.py文件,可以看出,该文件中已经默认添加了一下代码:```pythonimport scrapyclass NewsSpider(scrapy.Spider):name = 'news'allowed_domains = ['163.com']start_urls = ['http://163.com']def parse(self, response):pass
其实,news.py这个文件也可以由我们自行创建,并编写上面的代码,只不过通过命令创建可以免去我们写固定代码的麻烦。
要建立一个Spider,必须继承scrapy.Spider类,并且确定三个强制的属性和一个方法:
- name = “”:这个属性是爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
- allow_domains = []:是搜索的域名范围,也是爬虫的约束范围,规定爬虫只爬取这个域名下的网页,不在该域名下的URL会被忽略。
- start_urls = []:爬取的URL列表。爬虫是从这里开始抓取数据,所以,第一次下载数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
- parse(self, response):解析方法,每个初始URL完成下载后,该方法将会被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一的参数,主要作用如下:
- 负责解析返回的网页源代码(response.body),提取结构化数据(生成Item)- 生成需要下一页的URL请求(构建Request请求对象)
将start_urls的值修改为需要爬取的第一个URL:start_urls = ["http://tech.163.com/special/gd2016"]
修改parse()方法:
def parse(self, response):# 将提取的源代码写入到文件中with open("news.html", "w", encoding="utf-8") as fw: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对象中,保存每个商品的属性值:
import scrapyfrom bs4 import BeautifulSoupfrom News.items import NewsItemclass NewsSpider(scrapy.Spider):name = 'news'allowed_domains = ['163.com']# 起始URL地址列表offset = 1base_url = 'http://tech.163.com/special/gd2016'start_urls = [base_url]def parse(self, response):# 获取相应的HTML源代码html = response.body.decode('utf-8')print(type(html))# 使用BeautifulSoup对HTML进行解析soup = BeautifulSoup(html, 'lxml')# 获取ul标签下的所有li标签lis = soup.select('#news-flow-content li')# 遍历所有的li标签for li in list(lis):# 获取新闻标题news_name = list(li.select('div.titleBar h3 a'))[0].get_text()# 获取新闻链接news_link = list(li.select('div.titleBar h3 a'))[0]news_link = news_link.get('href')# 获取新闻摘要信息news_brief = list(li.select('div.newsDigest p'))[0].get_text().replace('\n', '').replace(' ', '')# 获取新闻来源news_source = list(li.select('div.newsBottom p.sourceDate span'))[0].get_text()# 获取时间news_time = list(li.select('div.newsBottom p.sourceDate'))[0].get_text().replace(news_source, '')# 构建Item对象item = NewsItem()item['news_name'] = news_nameitem['news_link'] = news_linkitem['news_brief'] = news_briefitem['news_source'] = news_sourceitem['news_time'] = news_time# 将item对象返回给引擎,由引擎再交给管道组件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()方法返回包含所有结果的列表。
