继续创作,加快生长!这是我参与「日新计划 10 月更文挑战」的第23天,点击查看活动概况
Spider实战
本文将解说怎么运用scrapy框架完结北京公交信息的获取。
方针网址为beijing.8684.cn/。
在前文的爬虫实战中,现已解说了怎么运用requests和bs4爬取公交站点的信息,感兴趣的话能够先阅览一下「Python」爬虫实战系列-北京公交线路信息爬取(requests+bs4) – (),在回来阅览这篇文章。
关于爬虫系列文章,这儿浅浅的罗列一下,欢迎阅览️️️:
「Python」爬虫-1.入门知识简介 – ()
「Python」爬虫-2.xpath解析和cookie,session – ()
「Python」爬虫-3.防盗链处理 – ()
「Python」爬虫-4.selenium的运用 – ()
「Python」爬虫-5.m3u8(视频)文件的处理 – ()
「Python」爬虫-6.爬虫功率的进步 – ()
「Python」爬虫-7.验证码的识别 – ()
「Python」爬虫-8.断点调试-网易云谈论爬取 – ()
1.相关技术介绍
scrapy是一个为了爬取网站数据,提取结构性数据而编写的运用框架,能够运用在包括数据挖掘,信息处理或存储历史数据等一列的程序中。
scrapy的操作流程如下:
1.挑选网站->2.创立一个scrapy项目->3.创立一个spider->4.界说item ->5.编写spider->6.提取item->7.存取爬取的数据->8.履行项目。
下面将顺次按照此流程来解说scrapy框架的运用:
1.挑选网站
本文选取的方针url为beijing.8684.cn/,经过之前的实验实战文章,信任读者对该网站现已有了开始的知道。
这儿相同咱们所需求爬取的信息为公交线路称号、公交的运营规模、运转时刻、参阅票价、公交所属的公司以及服务热线、公交来回线路的途径站点。
2.创立一个scrapy项目
在开始爬取之前,需求创立一个新的scrapy项目,切换到该项目途径的目录下,并履行scrapy startproject work
,
(其间work为项意图姓名,能够自拟,叫啥都可
观察work目录下的结构如下所示:
└─work
│ scrapy.cfg # 项目配置文件
│
└─work # 该项意图python模块
│ items.py # 界说爬取的数据结构
│ middlewares.py # 界说爬取时的中心件
│ pipelines.py # 数据管道,将数据存入本地文件或存入数据库
│ settings.py # 项意图设置文件
│ __init__.py
│
└─spiders # 放置spider代码的目录
__init__.py
3.创立一个spider。
在work
目录下,运用genspider
句子,创立一个spider。格局如下:
scrapy genspider spider_name url
spider_name为自己程序的姓名,自己为自己程序取名,不过分吧?
url为自己爬取的方针网址,本文为beijing.8684.cn/。
4.界说item
item是保存爬取到的数据容器,运用办法和python字典相似,并供给了额定保护机制来防止拼写过错导致的未界说字段过错。
首要,依据从方针网站获取到的数据对item 进行建模,并在item中界说相应的字段,编辑work
目录中的items.py
文件。比方:
import scrapy
class MyItem(scrapy.Item):
title = scrapy.Field()
desc = scrapy.Field()
经过界说item,能够很方面的运用scrapy的其他办法,而这些办法需求知道item 的界说。
5.编写spider
spider是用户编写用于从单个网站爬取数据的类,其间包括了一个用于下载的初始url,以及怎么跟进网页中的链接和分析页面中的内容,提取生成item的办法。
为了创立一个spider,有必要继承scrapy.Spider
类,而且有必要界说以下三个特点:
- name:用于区别Spider,该姓名有必要仅有,不能够为不同的spider设定相同的姓名
- start_urls: 包括了Spider在启动时进行爬取的url列表,因此,第一个被获取到的页面将是其间之一,后续的url则从初始的url获取到的数据中获取。
-
parse(): 是spider的一个办法,被调用时,每个初始url完结下载后生成的
Response
目标将会作为仅有的参数传递给该函数,该办法负责解析回来的数据(response data),提取数据(生成item)以及生成需求进一步处理的url的Request目标
。
6.提取item
从网页中提取数据的办法有许多,正如前面实验所用到的requests
或许selenium
等,scrapy运用了一种依据xpath和css表达式机制:scrapy selectors
,这儿给出常用xpath表达式对应的含义:
/html/head/title:挑选html文档中<head>标签内的<title>元素。
/html/head/title/text():挑选上面提到的<title>元素的文字。
//td:挑选一切的<td>元素
//div[@class="mine"]:挑选一切具有class="mine"特点的div元素。
为了配合xpath,scrapy除了供给selector之外,还供给了其他办法来防止每次从response中提取数据时生成selector的费事。
selector有四个根本的办法;
-
xpath()
:传入xpath表达式,回来该表示对应一切节点的selector list列表 -
css()
:传入css表达式,回来该表达式对应的一切节点的selector list列表 -
extract()
:序列化该节点为unicode字符串并回来list列表 -
re()
:依据传入的正则表达式对数据进行提取,回来unicode字符串list列表。
7.存取爬取的数据
scrapy还集成了存储数据的办法,运用指令如下:
scrapy crawl work -o items.json
该指令将选用json
格局对爬取的数据进行序列化,并生成对应的items.json
文件。对于小规模的项目,这种存储方式较为灵敏,假如对爬取到的item
做更多更为杂乱的操作就需求编写pipelines.py
。
8.履行项目
到当前项意图根目录,履行以下指令即可启动spider:
scrapy crawl work
scrapy为spider的start_urls
特点中的每一个url
创立了scrapy.Request
目标,并将parse
办法作为回调函数callback
赋值给了Request
.
Request目标
经过调度,履行生成scrapy.HTTP.Response
目标并送回给spider.parse()
办法。
cd 到 /work/work/
目录下,履行scrapy crawl BusCrawl
也能够启动spider。
2.全体爬取进程分析
2.1 settings.py
依据上述相关技术介绍,新建一个work
项目,并履行
genspider BusCrawl https://beijing.8684.cn/
。
经过genspider
之后,就会发现项意图BusCrawl.py
中主动为咱们现已增加了一些代码:
import scrapy
class BuscrawlSpider(scrapy.Spider):
name = 'BusCrawl'
allowed_domains = ['beijing.8684.cn']
start_urls = ['http://beijing.8684.cn']
def __init__(self, name=None, **kwargs):
super().__init__(name=None, **kwargs)
def start_requests(self):
pass
def parse(self, response):
pass
这儿由于咱们的公交线路衔接需求拼接list?
,所以在本文中start_urls
直接修正为字符串的方式。
settings.py
默许内容如下:
BOT_NAME = 'work'
SPIDER_MODULES = ['work.spiders']
NEWSPIDER_MODULE = 'work.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'work (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# LOG_LEVEL = 'WARNING'
items.py
默许内容如下:
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class BusCrawlPipeline:
def process_item(self, item, spider):
return item
然后修正settings.py
中robots
协议为False
以及粘贴自己的headers
,
robots协议本来是爬虫应该遵循的,可是假如遵循的话,信任大部分信息都设置为不行爬取,那么,就爬不到啥东西了
本文的详细设置为如下:
ROBOTSTXT_OBEY = False
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
"User-Agent": "xxx"
}
毕竟大部分的网站信息要是遵循robots协议如同都爬取不了。 user-agent直接自己去仿制就好了,办法这儿不再赘述。
middlewares.py
本文不触及,这儿省掉介绍。
2.2 BusCrawl.py
接下来需求写BusCrawl.py
,能够看到scrapy现已为咱们写好了部分函数,咱们需求依据自己的需求进一步完善相关的代码文件。
2.2.1 start_requests(self)
先将start_urls
切换为字符串的方式,由于咱们爬取的详细信息url
需求进一步结构。依据之前的分析,以每个数字或许字母开头的页面是直接在https://beijing.8684.cn/
后加上list?
。所以start_requests()
函数如下:
def start_requests(self):
for page in range(3):
url = '{url}/list{page}'.format(url=self.start_urls, page=page + 1)
yield FormRequest(url, callback=self.parse_index)
其间yield
函数就是函数履行到这就结束了,并调用了FormRequest()
函数,这个需求引进from scrapy import FormRequest
,然后将对页面进行详细分析的函数名传递给callback
,这样就能够调用parse_index
函数了。(parse_index
其实就是解析函数
留意这儿的parse_index
姓名不是固定的,这儿仅仅依据callback
来调用详细的函数。
运转程序之后是能够看到在控制台输出了咱们的list链接信息。
2.2.2 parse_index(self, response)
然后依据https://beijing.8684.cn/list?
规划perse_index
提取到每条详细线路的href
中的概况页面。parse_index
函数规划如下:
def parse_index(self, response):
hrefs = response.xpath('//div[@class="list clearfix"]/a/@href').extract()
for href in hrefs:
detail_url = urljoin(self.start_urls, href)
yield Request(detail_url, callback=self.parse_detail)
这儿需求留意:提取每个list
里边的线路详细链接的时分直接仿制的xpath
代码用不了,需求依据class名才干提取到,经过extract()
即可提取到href
的信息。
response.xpath('//div[@class="list clearfix"]/a/@href').extract()
extract():这个办法回来的是一个数组list
extract_first():这个办法回来的是一个string字符串,是list数组里边的第一个字符串。
而yield Request(...)
之后就能够看到概况界面的链接了,说明咱们的函数写的十分正确!
运用yield
之后,会默许在控制台打印信息,而不需求咱们自己手动print打印了。
2.2.3 parse_detail(self, response)
提取到每条公交线路的概况页面的url
之后,就能够开始对概况页面进行信息提取了。这儿 的分析办法和之前的bs4
或许xpath
解析差不多,稍微改一改就能够直接拿过来用了。
这儿遇到的问题和之前的相似,在提取公交的站点信息的时分,仍然会呈现中心路线呈现了终点站或开始站的状况,这儿的解决办法和之前相似->遍历中心的站点,然后删去开始站点。
未处理前爬取的站点信息图如下:
详细的解析页面的函数规划如下:
def parse_detail(self, response):
title = response.xpath('//h1[@class="title"]/span/text()').extract_first() # 线路称号
category = response.xpath('//a[@class="category"]/text()').extract_first() # 线路类别
time = response.xpath('//ul[@class="bus-desc"]/li[1]/text()').extract_first() # 开车时刻
price = response.xpath('//ul[@class="bus-desc"]/li[2]/text()').extract_first() # 参阅票价
company = response.xpath('//ul[@class="bus-desc"]/li[3]/a/text()').extract() # 所属公司
trip = response.xpath('//div[@class="trip"]/text()').extract() # 开车方向
path_go, path_back = None, None
begin_station, end_station = trip[0].split('—')
if len(trip) > 1:
path_go = response.xpath('//div[@class="service-area"]/div[2]/ol/li/a/text()').extract()
path_back = response.xpath('//div[@class="service-area"]/div[4]/ol/li/a/text()').extract()
go_list = [begin_station] + [station for station in path_go[1:-1] if station != end_station] + [end_station]
back_list = [end_station] + [station for station in path_back[1:-1] if station != begin_station] + [
begin_station]
path_go = {trip[0]: '->'.join(go_list)}
path_back = {trip[1]: '->'.join(back_list)}
else:
path_go = response.xpath('//div[@class="bus-lzlist mb15"]/ol/li/a/text()').extract()
go_list = [begin_station] + [station for station in path_go[1:-1] if station != end_station] + [end_station]
path_go = {trip[0]: '->'.join(go_list)}
item = {'title': title,
'category': category,
'time': time,
'price': price,
'company': company,
'trip': trip,
'path_go': path_go,
'path_back': path_back,
}
bus_info = WorkItem() # 实例化item
for field in bus_info.fields:
bus_info[field] = eval(field)
yield bus_info
需求留意的是,并不是一切的公交线路的开始站点和终点站都不相同。在处理信息的进程中呈现了开始站和终点站相同的状况,如下图的五间楼-五间楼
。
这儿需求对其进行进一步的处理,这儿直接增加的if
判别,详细完成见上面的代码。。
bus_info = WorkItem() # 实例化item
for field in bus_info.fields:
bus_info[field] = eval(field)
yield bus_info
这一段代码能够笼统为一个模板,将自己需求提取的信息放到bus_info
里边即可。然后yield bus_info
。前面也提到过item
的运用办法和字典相似。
2.3 items.py
接下来写`items.py:
items.py
中的信息称号需求和parse_detail
中的实例化的item名对应起来。
本文详细函数规划如下:
class WorkItem(scrapy.Item):
title = scrapy.Field()
category = scrapy.Field()
time = scrapy.Field()
price = scrapy.Field()
company = scrapy.Field()
trip = scrapy.Field()
path_go = scrapy.Field()
path_back = scrapy.Field()
tips:
scrapy.Field(),相似于字典。在spider中实例化,即
parse_detail()
中bus_info = WorkItem()
实例化,然后取值的方式相似于字典,即代码中的bus_info[field] = eval(field)
。
2.4 pipelines.py
接下来需求规划写入到数据库的pipelines.py
。本文以写入本地的MongoDB数据库为例,MongoDB数据库的版本为6.x。
首要在settings.py
中增加
# 启用pipeline
ITEM_PIPELINES = {
'work.pipelines.MyMongoPipeline': 300,
}
# 数据库相关设置
DB_HOST = 'localhost'
DB_NAME = 'bus_spider'
DB_COLLECTION_NAME = 'ipad'
ITEM_PIPELINES
中的300
是分配给每个类的整型值,确定了他们运转的次序,item按数字从低到高的次序,经过pipeline,通常将这些数字界说在0-1000规模内(0-1000随意设置,数值越低,组件的优先级越高)。
item pipiline组件是一个独立的Python类,其间process_item()
办法有必要完成,item pipeline一般运用在:
- 验证爬取的数据(查看item包括某些字段,比方说name字段)
- 查重(并丢弃)
- 将爬取结果保存到文件或许数据库中
这儿规划的pipelines.py
是将数据存储到本地的MongoDB中,py衔接MongoDB就不必详细说了,详细规划如下:
from pymongo import MongoClient
from .settings import *
class MyMongoPipeline:
def __init__(self):
self.client = MongoClient(host=DB_HOST)
self.db = self.client.get_database(DB_NAME)
self.collection = self.db[DB_COLLECTION_NAME]
def process_item(self, item, spider):
# 这个办法有必要回来一个 Item 目标,被丢弃的item将不会被之后的pipeline组件所处理
self.collection.insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()
假如想将数据存为json的格局,那么能够参阅以下代码:
import json
class ItcastJsonPipeline(object):
def __init__(self):
self.file = open('xxx.json', 'wb')
def process_item(self, item, spider):
content = json.dumps(dict(item), ensure_ascii=False) + "\n" # 这儿先对item进行了转型-变为dict
self.file.write(content)
return item
def close_spider(self, spider):
self.file.close()
3.代码部分
3.1 运转界面
爬取数据成功界面
mongodb数据查询界面
3.2 详细代码
# Buscrawl.py
import scrapy
from scrapy import FormRequest, Request
import logging
from urllib.parse import urljoin
from ..items import WorkItem
logging.getLogger("filelock").setLevel(logging.INFO)
class BuscrawlSpider(scrapy.Spider):
name = 'BusCrawl'
allowed_domains = ['beijing.8684.cn']
start_urls = 'http://beijing.8684.cn'
def __init__(self, name=None, **kwargs):
super().__init__(name=None, **kwargs)
def start_requests(self):
for page in range(3):
url = '{url}/list{page}'.format(url=self.start_urls, page=page + 1)
yield FormRequest(url, callback=self.parse_index)
def parse_index(self, response):
hrefs = response.xpath('//div[@class="list clearfix"]/a/@href').extract()
for href in hrefs:
detail_url = urljoin(self.start_urls, href)
yield Request(detail_url, callback=self.parse_detail)
def parse_detail(self, response):
title = response.xpath('//h1[@class="title"]/span/text()').extract_first() # 线路称号
category = response.xpath('//a[@class="category"]/text()').extract_first() # 线路类别
time = response.xpath('//ul[@class="bus-desc"]/li[1]/text()').extract_first() # 开车时刻
price = response.xpath('//ul[@class="bus-desc"]/li[2]/text()').extract_first() # 参阅票价
company = response.xpath('//ul[@class="bus-desc"]/li[3]/a/text()').extract() # 所属公司
trip = response.xpath('//div[@class="trip"]/text()').extract() # 开车方向
path_go, path_back = None, None
begin_station, end_station = trip[0].split('—')
if len(trip) > 1:
path_go = response.xpath('//div[@class="service-area"]/div[2]/ol/li/a/text()').extract()
path_back = response.xpath('//div[@class="service-area"]/div[4]/ol/li/a/text()').extract()
go_list = [begin_station] + [station for station in path_go[1:-1] if station != end_station] + [end_station]
back_list = [end_station] + [station for station in path_back[1:-1] if station != begin_station] + [
begin_station]
path_go = {trip[0]: '->'.join(go_list)}
path_back = {trip[1]: '->'.join(back_list)}
else:
path_go = response.xpath('//div[@class="bus-lzlist mb15"]/ol/li/a/text()').extract()
go_list = [begin_station] + [station for station in path_go[1:-1] if station != end_station] + [end_station]
path_go = {trip[0]: '->'.join(go_list)}
item = {'title': title,
'category': category,
'time': time,
'price': price,
'company': company,
'trip': trip,
'path_go': path_go,
'path_back': path_back,
}
bus_info = WorkItem()
for field in bus_info.fields:
bus_info[field] = eval(field)
yield bus_info
# items.py
import scrapy
class WorkItem(scrapy.Item):
title = scrapy.Field()
category = scrapy.Field()
time = scrapy.Field()
price = scrapy.Field()
company = scrapy.Field()
trip = scrapy.Field()
path_go = scrapy.Field()
path_back = scrapy.Field()
# pipelines.py
from pymongo import MongoClient
from .settings import *
class MyMongoPipeline:
def __init__(self):
self.client = MongoClient(host=DB_HOST)
self.db = self.client.get_database(DB_NAME)
self.collection = self.db[DB_COLLECTION_NAME]
def process_item(self, item, spider):
self.collection.insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()
# settings.py
BOT_NAME = 'work'
SPIDER_MODULES = ['work.spiders']
NEWSPIDER_MODULE = 'work.spiders'
ROBOTSTXT_OBEY = False
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
"User-Agent": "xxx"
}
ITEM_PIPELINES = {
'work.pipelines.MyMongoPipeline': 300,
}
DB_HOST = 'localhost'
DB_NAME = 'bus_spider'
DB_COLLECTION_NAME = 'ipad'
4.可能呈现的报错:
1.找不到WorkPipeline
原因:自己修正了pipelines.py
里边的类名,可是没有修正settings.py
里边的pipelines称号。
解决办法:settings.py
中的ITEM_PIPELINES
里边的.MyMongoPipeline
一定要和pipelines.py
里边的类名对上。
ITEM_PIPELINES = {
'work.pipelines.MyMongoPipeline': 300,
}
2. [filelock] DEBUG: Attempting to acquire lock 4469884432 on …… __a22fb8__tldextract-3.3.1/
原因:其实不是报错,假如不想在控制台的看到的话,直接设置一下logging的打印等级即可。
解决办法:
import logging
logging.getLogger("filelock").setLevel(logging.INFO)
参阅:github.com/scrapy/scra…
3..raise KeyError(f”{self.class.name} does not support field: {key}”)
原因:KeyError: ‘WorkItem does not support field: _id’
解决办法:将item转为dict类型即可,即在刺进数据时运用:
self.collection.insert(dict(item))
参阅链接
1.blog.csdn.net/m0_59483606…
2.blog.csdn.net/songrenqing…