Scrapy: збираємо дані і зберігаємо в базу даних

Введення
Мене зацікавив даний фреймворк для збору інформації з сайтів. Тут були публікації з Scrapy, але оскільки детальної інформації російською мовою мало, то я хотів би розповісти про свій досвід.

Завдання
  1. Зайти на сторінку зі списком абітурієнтів oreluniver.ru/abits?src=all_postupil. Потім пройти по кожному посиланні і зібрати дані про абітурієнтів та набраних ними балів.
  2. З самої ж сторінки, зібрати дані про спеціальності, на які вівся набір.
  3. Зберегти всі результати в базу даних
Рішення
Для вирішення завдання я використовував 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.ua"]
start_urls = ["http://oreluniver.ru/abits?src=all_postupil"]

rules = (
Rule(LinkExtractor(allow=('spec_id=')), 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.ua"]
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

Список джерел
  1. Документація Scrapy
  2. Збираємо дані з допомогою Scrapy
  3. Sqlalchemy
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.