Введение
Меня заинтересовал данный фреймворк для сбора информации с сайтов. Здесь были публикации по Scrapy, но поскольку детальной информации на русском языке мало, то я хотел бы рассказать о своем опыте.
Задача
- Зайти на страницу со списком абитуриентов oreluniver.ru/abits?src=all_postupil. Затем пройти по каждой ссылке и собрать данные о поступивших абитуриентах и набранных ими баллах.
- С самой же страницы, собрать данные о специальностях, на которые велся набор.
- Сохранить все результаты в базу данных
Решение
Для решения задачи я использовал Python 2.7, Scrapy 1.1 Sqlalchemy 1, Sqlite. Установил все как описано в документации. В статье также описана установка на русском языке, там же о создании самого паука. Вот что у меня получилось.
Структура проекта:
\spiders
\spiders\__init__.py
\spiders\abiturlist.py
\spiders\SpecSpider.py
__init__.py
items.py
pipelines.py
settings.py
Файл items.py
from scrapy.item import Item, Field class SpecItem(Item): spec = Field() SpecName = Field() class GtudataItem(Item): family = Field() name = Field() surname = Field() spec = Field() ball = Field() url = Field() pagespec = Field()
Здесь описан класс паука для получения списка абитуриентов.
Файл abiturlist.py
from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from scrapy.loader.processors import TakeFirst, Identity from scrapy.loader import ItemLoader from scrapy.selector import HtmlXPathSelector, Selector from gtudata.items import GtudataItem class AbiturLoader(ItemLoader): default_output_processor = Identity() class AbiturlistSpider(CrawlSpider): name = "abiturlist" allowed_domains = ["oreluniver.ru"] start_urls = ["http://oreluniver.ru/abits?src=all_postupil"] rules = ( Rule(LinkExtractor(allow=('spec_id=04.03.01')), callback='parse_item'), ) def parse_item(self, response): hxs = Selector(response) all = hxs.xpath("//tr[position()>1]") pg_spec = hxs.xpath("//div[@class='page-content']/b/div/text()").extract()[0].strip() for fld in all: Item = GtudataItem() FIO = fld.xpath("./td[2]/p/text()").extract()[0].split() Item['family'] = FIO[0] Item['name'] = FIO[1] Item['surname'] = FIO[2] Item['spec'] = fld.xpath("./td[last()]/p/text()").extract()[0] ball = fld.xpath("string(./td[3]/p)").extract()[0] Item['ball'] = ball Item['url'] = response.url Item['pagespec'] = pg_spec yield Item
Здесь описан класс паука для сбора списка специальностей. Для определения полей использован Xpath. Для разделения номера специальности и ее названия используем срезы, номер специальности занимает 9 символов.
Файл SpecSpider.py
from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from scrapy.selector import HtmlXPathSelector, Selector from gtudata.items import SpecItem class SpecSpider(CrawlSpider): name = "speclist" allowed_domains = ["oreluniver.ru"] start_urls = ["http://oreluniver.ru/abits?src=all_postupil"] # /abits?src=all_postupil rules = ( Rule(LinkExtractor(allow = ('src=all_postupil')), callback='parse_item'), ) def parse_item(self, response): hxs = Selector(response) all = hxs.xpath('//a[contains(@href, "spec_id")]/text()').extract() # print 'test' for fld in all: txt = fld.strip() Item = SpecItem() Item['SpecName'] = txt[9:] Item['spec'] = txt[:8] yield Item
Хотелось бы отметить возможность создания нескольких классов для собираемых данных в файле Items.py. В моем случае:
- SpecItem — для списка специальностей;
- GtudataItem — для данных абитуриентов.
Сохранение результатов в базу данных
В файле pipelines.py описаны действия по сохранению данных. Для создания базы данных sqlite с заданной структурой таблиц я использовал sqlalchemy.
Во первых создаем экземпляр класса declarative_base(), от которого будем наследовать классы, для описания таблиц базы данных, в которых будем сохранять найденную информацию. Это классы SpecTable, для сохранения списка специальностей, и DataTable, для сохранения данных абитуриентов.
В каждом классе задаем атрибут __tablename__. Это имя таблицы в базе данных. затем задаем поля:
id = Column(Integer, primary_key=True)
id — целое, первичный ключ.
Остальные поля, например номер специальности:
spec = Column(String)
В методе __init__() заполняем поля таблицы.
В классе GtudataPipeline описан процесс работы с базой данных. при инициализации проверяем наличие файла базы данных в папке проекта. Если файл отсутствует, то создаем базу данных с заданной структурой.
Base.metadata.create_all(self.engine)
В методе process_item описываем собственно сохранение в базу данных. Проверяем, экземпляром какого класса является item. В зависимости от этого заполняем одну из двух таблиц. Для этого создаем экземпляры классов DataTable и SpecTabl.
dt = DataTable(item['family'],item['name'], item['surname'], item['spec'], item['ball'], item['url'], item['pagespec']) dt = SpecTable(item['spec'],item['SpecName'])
Для обеспечения уникальности сохраняемых данных (абитуриенты в таблицах могут повторяться) используем атрибут fio. это множество, элементы которого формируются следующей строчкой.
fio = item['family'] + item['name'] + item['surname']
Если такой абитуриент имеется в базе, то запись не сохраняется.
if fio not in self.fio: dt = DataTable(item['family'],item['name'], item['surname'], item['spec'], item['ball'], item['url'], item['pagespec']) self.fio.add(fio) self.session.add(dt)
Добавляем новую запись:
self.session.add(dt)
При открытии паука создаем сессию:
def open_spider(self, spider): self.session = Session(bind=self.engine)
При закрытии паука завершаем изменения:
def close_spider(self, spider): self.session.commit() self.session.close()
Вот что в итоге получилось:
Файл pipelines.py
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey from sqlalchemy.orm import Session import os from gtudata.items import SpecItem, GtudataItem from scrapy.exceptions import DropItem Base = declarative_base() class SpecTable(Base): __tablename__ = 'specdata' id = Column(Integer, primary_key=True) spec = Column(String) spectitle = Column(String) def __init__(self, spec, spectitle): self.spec= spec self.spectitle = spectitle def __repr__(self): return "<Data %s, %s>" % (self.spec, self.spectitle) class DataTable(Base): __tablename__ = 'gtudata' id = Column(Integer, primary_key=True) family = Column(String) name = Column(String) surname = Column(String) spec = Column(String) ball = Column(Integer) url = Column(String) pagespec = Column(String) def __init__(self, family, name, surname, spec, ball, url, pagespec): self.family = family self.name = name self.surname = surname self.spec = spec self.ball = ball self.url = url self.pagespec = pagespec def __repr__(self): return "<Data %s, %s, %s, %s, %s, %s, %s>" % \ (self.family, self.name, self.surname, self.spec, self.ball, self.url, self.pagespec) class GtudataPipeline(object): def __init__(self): basename = 'data_scraped' self.engine = create_engine("sqlite:///%s" % basename, echo=False) if not os.path.exists(basename): Base.metadata.create_all(self.engine) self.fio = set() def process_item(self, item, spider): if isinstance(item, GtudataItem): fio = item['family'] + item['name'] + item['surname'] if fio not in self.fio: dt = DataTable(item['family'],item['name'], item['surname'], item['spec'], item['ball'], item['url'], item['pagespec']) self.fio.add(fio) self.session.add(dt) elif isinstance(item, SpecItem): dt = SpecTable(item['spec'],item['SpecName']) self.session.add(dt) return item def close_spider(self, spider): self.session.commit() self.session.close() def open_spider(self, spider): self.session = Session(bind=self.engine)
Запускаем в папке с проектом:
scrapy crawl speclist scrapy crawl abiturlist
И получаем результат. Полная версия проекта выложена на GitHub
