Введение
Меня заинтересовал данный фреймворк для сбора информации с сайтов. Здесь были публикации по 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