Качаємо останню версію freebase-python. Розпаковуємо архів, переходимо в нього і пишемо:

sudo python setup.py

Після цього пробуємо виконати простенький запит, наприклад дізнатись хто режисер фільму Hot Fuzz:

import freebase 
print freebase.mqlread({"name":"Hot Fuzz","type":"/film/film","directed_by":[]})

Словник що передається у функцію - запит MQL. Запити MQL записуються в форматі JSON, і всі поля що містять значення "[]", або "null" заповнюються даними з бази. Детальна інструкція по MQL тут.


Архітектура Metaweb

ред.

Тепер трохи відійдемо від практики, і розглянемо структуру бази даних.

База даних Metaweb відрізняється від звичайних реляційних БД. Вона являє собою граф з вузлів та відношень. Також є додаткова частина - сховище медіа (content store). Вона призначена для зберігання бінарних файлів (BLOB в SQL), кожному з яких відповідає вузол графа. Ключем до файлу є його SHA2 хеш, це забезпечує відсутність дублікатів.

Вузли та зв'язки

ред.

На найнижчому рівні база даних Metaweb - це граф вузлів та зв'язків між ними. Кожен вузол має унікальний ідентифікатор та запис про те коли, й ким він був створений. Окрім ідентифікатора дати та автора вузли не містять жодної інформації. Все найцікавіше зберігається в зв'язках між вузлами (чи між вузлами та примітивами).

Зв'язки - це кортежі з 4 елементів (насправді 6, зв'язок теж має автора та час створення). Перший елемент From - перший (лівий) вузол зв'язку. Другий - Property - тип зв'язку. Наступне значення - To другий (правий) вузол зв'язку. Value - значення. Вузол може перебувати у відношенні з іншим вузлом, або зі значенням, або з обома одночасно, залежно від типу (Property) зв'язку.

Наприклад:

From Property To Value
/en/the_police /type/object/name /lang/en The Police
/en/zenyatta_mondatta /type/object/name /lang/en Zenyatta Mondatta
/en/zenyatta_mondatta /music/album/artist /en/the_police

Перший рядок каже, що вузол /en/the_police, пов'язаний зв'язком /type/object/name (ім'я) з вузлом /lang/en (англійська мова) та значенням "The Police", тобто англійською мовою вузол /en/the_police називається "The Police". Другий дає подібну інформацію про альбом "Zenyatta Mondatta". Третій - каже про те, що альбом та група пов'язані через /music/album/artist, тобто "Zenyatta Mondatta" - альбом групи "The Police".

Властивості

ред.

Властивості об'єктів (вузлів) в БД задаються типами зв'язків. Можна було помітити що колонка Property попередній таблиці містить ідентифікатори подібні до ідентифікаторів вузлів. А все тому, що тип зв'язку теж є вузлом, і його теж можуть створити користувачі, і він теж може попадати в поля From та To. Наприклад:

From Property To Value
/type/object/name /type/object/name /lang/en Name
/music/album/artist /type/object/name /lang/en Artist
/music/track/album /type/object/name /lang/en Appears On
/music/album/artist /type/property/unique true
/music/track/album /type/property/unique false

Перший рядок цієї таблиці має доволі цікавий сенс. Він каже нам про те, що ім'я властивості ім'я англійською мовою "Name".

Напрямки властивостей

ред.

Напевне досить очевидно, що граф бази даних - напрямлений, так як для зв'язку конкретно вказано куди і звідки. Але Metaweb вміє переміщуватись по графу в обидві сторони.

Наприклад розглянемо зв'язок між альбомами та треками. Кожна композиція (трек) має властивість "альбом[и]" в яких вона була видана. Ми можемо здійснити запит "дізнатись в яких альбомах публікувалась Driven to Tears", і Metaweb знайде всі значення поля "To" кортежів, в яких поле "From" містить "Driven to Tears", а поле "Property" - /music/track/album. Можна задати протилежний запит - "які треки містить альбом Zenyatta Mondatta?". Metaweb дасть нам значення поля "From" всіх кортежів які містять "Zenyatta Mondatta" в полі "To", та /music/track/album в "Property".

Metabase має як властивість альбоми в яких видавалась композиція (/music/track/album), так і протилежну - композиції альбому. (/music/album/track). Такі властивості називаються взаємними (англ. reciprocal properties), і такий їх зв'язок теж описується в графі:

From Property To Value
/music/track/album /type/property/reverse_property /music/album/track

Значення властивостей

ред.

Деякі властивості є базовими, тобто вбудовані в архітектуру і мають чітко прописане значення. Наприклад на /type/object/name накладається обмеження, що кожен об'єкт хоча й може мати багато імен, але лише одне кожною мовою. Іншою властивістю вбудованою в архітектуру є наприклад /type/property/unique, яка показує чи властивість вказує на один об'єкт, чи на множину.

Щоправда більшість властивостей не такі. /music/album/artist не є частиною архітектури. Його створили користувачі freebase.com для запису інформації про музику. Сам ідентифікатор властивості дає нам підказку про те що вона означає, яка підтверджується тим, що ця властивість має ім'я "Artist" (через зв'язок /type/object/name). Ми навіть можемо отримати коротку інструкцію використання властивості, якщо використаємо властивість /freebase/documented_object/tip.

Таким чином значення /type/property/unique закодоване прямо в реалізації БД, а значення /music/album/artist існує тільки в головах користувачів.

Назви, ідентифікатори та простори імен

ред.

Кожен вузол має унікальний ідентифікатор, але не зобов'язаний мати унікальне ім'я. Вузли /lang/en, /en/english_people, та /authority/gnis/57724 відповідають мові, нації, та місту в штаті Акранзас, і всі мають однакову назву англійською - "English". А якщо є два вузли що мають однакові ідентифікатори - то це один і той самий вузол. Ідентифікатори не є незмінними, їх можна редагувати як і вузли. Можна змінити ідентифікатор вузла, і можна зробити так щоб ідентифікатор показував на інший вузол.

Такий ідентифікатор як наприклад /type/object/name складається з двох частин - /type/object, яка називається простором імен (і в свою чергу теж є ідентифікатором з простору імен /type), та name - який називається ключем. Це трохи нагадує структуру директорій Unix.

Ідентифікатори на зразок /en/the_police написані майже англійською мовою, тому досить зрозумілі. Щоправда такі ідентифікатори вдається надати не всім вузлам, тому окрім них, кожен вузол ідентифікується ключами з простору імен guid (англ. globally-unique identifier), наприклад пісня "Driven to Tears" має ідентифікатор /guid/9202a8c04000641f800000000129a87a. На рівні реалізації БД, зв'язки між вузлами встановлюються записом в поля кортежу From, To та Property саме цих ідентифікаторів, а не тих, що були показані в таблицях. guid - фундаментальна властивість кожного вузла, на відміну від інших ідентифікаторів. Ці інші ідентифікатори можна отримати через зв'язки типу /type/object/key де в полі "To" дається простір імен, а в "Value" - ключ. Простір імен теж вузол, тому теж має ідентифікатор.

Об'єкти та типи

ред.

Дотепер ми казали що база даних складається з вузлів та зв'язків, і старанно уникали слова "об'єкт" (можете перевірити). Але воно зустрічалось в таких ідентифікаторах як наприклад /type/object/key, чи /freebase/documented_object/tip. Тому, хоча дуже потрібно знати що на найнижчому рівні Freebase складається з кортежів що задають зв'язки між вузлами, але варто поглянути на неї через об'єктно-орієнтовані окуляри O^O.

Тому замість того щоб думати про зв'язки між вузлами пов'язаними з вузлом групи "The Police" дані можна представити таким псевдокодом:

{
  id: "/en/the_police",
  name: "The Police",
  /music/artist/album: {
    id: "/en/zenyatta_mondatta",
    name: "Zenyatta Mondatta",
    /music/album/track: {
      name: "Driven to Tears",
      /music/track/length: 200.266
    }
    /music/album/track: {
      name: "Canary in a Coalmine",
      /music/track/length: 146.506
    }
  }
}

Нагадує JSON, правда? Це не випадково. Ми бачимо об'єкт, де id = /en/the_police та name = "The Police". id та name - скорочення що відповідають універсальним властивостям /type/object/id та /type/object/name, які доступні всім об'єктам. Всередині нього є об'єкт що відповідає альбому, а всередині альбому - об'єкти для треків (загалом їх більше, але вони були опущені для лаконічності). Відомості про альбоми та треки покладаються на властивості /music/artist/album та /music/album/track.

Щоб завершити огляд Freebase з об'єктно-орієнтованого погляду потрібно розповісти про типи. Типи - це колекції однотипних властивостей, і об'єкт в Metaweb може бути екземпляром одного чи кількох типів. /en/the_police - екземпляр типу /music/artist, /en/zenyatta_mondatta - екземпляр типу /music/album, і об'єкт що представляє трек є екземпляром /music/track. Типи, як і властивості інше представлені вузлами. Властивості є екземплярами типу /type/property, а кожен тип - екземпляр /type/type (що означає що /type/type - екземпляр самого себе!) Об'єкти що мають властивості використовують свої ідентифікатори в якості простору імен для ідентифікаторів типів цих властивостей. Не переживайте якщо не зрозуміли попереднє речення, я й сам його ледве зрозумів. Поясню на прикладах. Властивості /music/track, такі як /music/track/album чи /music/track/length, мають ідентифікатори що починаються з /music/track.

Типи та властивості пов'язані унікальною властивістю /type/property/schema, яка описує зв'язок між властивістю, та типом до якого ця властивість входить. Зворотня властивість /type/type/properties дає всі властивості типу.

Існує ще дві, важливіші властивості що включають типи. Це /type/object/type, що аналогічно до /type/object/name використовується практично для кожного об'єкта в базі. Задає всі типи, екземплярами яких є об'єкт. Наприклад /en/the_police є екземляром /music/artist, /common/topic, /music/producer, та /music/musical_group.

Знати тип потрібно з двох причин: якщо ми знаємо що об'єкт - це /music/artist, ми знаємо що є сенс запитати про значення його властивості /music/artist/album. І тип об'єкта - чудовий уточнювач. Ми можемо знайти багацько об'єктів що називаються "English", але можемо суттєво звузити пошук, якщо скажемо що нас цікавлять лише екземпляри /type/lang.

На додачу до /type/object/type є ще одна властивість пов'язана з типами. Це унікальна властивість /type/property/expected_type (очікуваний тип), що для кожної властивості задає тип значення пов'язаного з цією властивістю. Наприклад очікуваним типом властивості /music/artist/album є /music/album, а очікуваним типом /music/album/track є /music/track.

Додавання типу об'єкта дозволяє нам суттєво спростити його запис:

{
  id: "/en/the_police",
  type: "/music/artist",
  name: "The Police",
  album: {
    id: "/en/zenyatta_mondatta",
    name: "Zenyatta Mondatta",
    track: {
      name: "Driven to Tears",
      length: 200.266
    }
    track: {
      name: "Canary in a Coalmine",
      length: 146.506
    }
  }
}

Ми додали в зовнішній об'єкт властивість type, який задав тип /music/artics. Це дозволяє нам використати просте ім'я властивості замість /music/artist/album. Більше того, так як ми знаємо що очікуваним типом для /music/artist/album є /music/album, ми можемо простом використати ім'я властивості track замість /music/album/track. Аналогічно /music/track/length скорочується до length.

Metaweb Query Language

ред.

Для зручності дослідження Freebase, та вивчення мови запитів, розробники створили "Query Editor" - зручний інструмент що працює прямо в браузері, та має підказки з автодоповненням та інші зручності.

Як вже згадувалось вище, MQL базується на JSON, тобто структура даних аналогічна до тієї яку ми можемо створити з словників, масивів та базових типів (рядків та чисел) Python. Відмінність між JSON та Python покажемо в таблиці:

Python JSON Значення
None null Немає даних
False false
True true
'"' "\"" Рядки в JSON - тільки в подвійних лапках

На цьому здається все. Відмінностей від Python не так і багато.

Спробуємо написати простий запит, який наприклад нам дасть список властивостей, які характеризують тип /film/film:

[{
  "id": "/film/film",
  "type": "/type/type",
  "properties": []
}]

В результаті виконання запиту, в правому полі (якщо ви писали запит в Query Editor) буде приблизно такий текст:

{
  "code":          "/api/status/ok",
  "result": [{
    "id":   "/film/film",
    "properties": [
      "/film/film/initial_release_date",
      "/film/film/directed_by",
      далі багато властивостей пропущено для компактності
    ],
    "type": "/type/type"
  }],
  "status":        "200 OK",
  "transaction_id": "cache;cache03.p01.sjc1:8101;2011-04-02T19:19:20Z;0041"
}


Одиничні та множинні результати

ред.

Як вже було сказано, відповідь на запит - це цей же запит, з заповненими полями, які мали значення [] чи null. Цю відповідь ми можемо бачити в полі "result" відповіді. Окрім "result" є інші поля з службовою інформацією.

А тепер спробуємо написати в нашому запиті замість [] null. Тобто так:

[{
  "id": "/film/film",
  "type": "/type/type",
  "properties": []
}]

Відповідь сервера зміниться. Він повідомить нас про помилку:

{
  "code":          "/api/status/error",
  "messages": [{
    "code":    "/api/status/error/mql/result",
    "info": {
      "count": 52,
      "result": [
        "/film/film/initial_release_date",
        "/film/film/directed_by",
        знову багато пропущено...
      ]
    },
    "message": "Unique query may have at most one result. Got 52",
    "path":    "properties",
    "query": [{
      "error_inside": "properties",
      "id":           "/film/film",
      "properties":   null,
      "type":         "/type/type"
    }]
  }],
  "status":        "200 OK",
  "transaction_id": "cache;cache03.p01.sjc1:8101;2011-04-02T19:25:14Z;0013"
}

Суть помилки пояснюють в полі "message": написавши в полі null ми очікуємо що база даних заповнить його значенням, але база даних знайшла їх аж 52 штуки. Простим вирішенням помилки є повернення до попередньої версії запиту, коли ми просили базу даних заповнити список.

Тут варто зауважити що навіть об'єкт може мати навіть кілька типів, тому наприклад запит

[{ "id": "/film/film", "type":null }]

теж дасть аналогічну помилку. З іншого боку, якщо навіть властивість може мати лише одне значення (як наприклад id), ми все одно можемо запитати не через null, а через []. Тоді потрібне нам значення потрапить в перший елемент списку.

Так само працює зовнішній запит. Якщо заповнити всі дані можна лише одним способом, то ми могли б написати наприклад:

{
  "name":"Ukrainian"
  "id":null,
  "type":[]
}

але база даних скаже що результатів аж ніяк не один, тому треба буде помістити цей запит всередину списку []. Ну, або уточнити тип аби залишився лише один результат.

Вкладені запити

ред.

Як ми знаємо, властивості - це теж вузли, і вони теж мають властивості, такі як, наприклад, ім'я, очікуваний тип, та якусь документацію. Ми уточнюємо наш запит вставивши всередину списку "properties" підзапит що задає поля властивостей, які нас цікавлять:

[{
  "id":   "/film/film",
  "type": "/type/type",
  "properties": [{
    "id": null,
    "name":          null,
    "expected_type": null,
    "/freebase/documented_object/tip": null
  }]
}]

Так само ми можемо зробити з акторами що грають в фільмі. Поле "starring" має очікуваний тип /film/performance, всередині якого збегігають дані про актора та персонажа якого він грає. Тому ми можемо написати такий запит:

[{
  "name":"Yes man",
  "type":"/film/film",
  "starring":[{"actor":null, "character":null}]
}]

і отримаємо в відповіді такий текст:

"result": [{
    "name": "Yes Man",
    "starring": [
      {
        "actor":     "Terence Stamp",
        "character": "Terrence Bundley"
      },
      {
        "actor":     "Bradley Cooper",
        "character": "Peter"
      }, 
      і так далі

ми можемо зробити ще один вкладений запит, і наприклад попросити вказувати окрім імені ще і стать актора:

[{
  "name": "Yes man",
  "type": "/film/film",
  "starring": [{
    "actor": {
      "name":          null,
      "/people/person/gender": null
    },
    "character": null
  }]
}]

Зауважте що властивість /people/person/gender вказана повністю. Це потрібно тому, що актор має очікуваний тип /film/actor, а тип /film/actor не передбачає жодної статі. Зате кожен конкретно взятий актор також є людиною, тому також належить типу /people/person/, який вже має таку властивість як gender.

Помінявши поля в яких ми вказуємо null, і заповнивши наприклад ім'я актора, можна отримати всі фільми в яких знімалась Зоуі Дешанель, разом з персонажами яких вона грала, якщо такі відомі:

[{
  name:null,
  "type": "/film/film",
  "starring": [{
    "actor": {
      "name": "Zooey Deschanel"
    },
    "character": null
  }]
}]

Аналогічний, і навіть трохи краще оформлений результат можна отримати вивернувши запит навиворіт:

[{
  "name": "Zooey Deschanel",
  "type": "/film/actor",
  "film": [{
      "film": null,
      "character": null
    }]
}]

Тепер в кожному фільмі не буде явно вказано те, що в ньому грала Зоуі Дешанель, а просто даний список фільмів та персонажів.

Основні властивості

ред.

Фільм в попередньому запиті, як і персонаж описувався своєю назвою:

        "character": "Trillian",
        "film":      "The Hitchhiker's Guide to the Galaxy"

Але ми вже точно знаємо, що фільм, на відміну від персонажа, про якого в базі даних може справді бути лише одне ім'я - це складний об'єкт з купою властивостей, а не один рядок тексту. А суть в тому, що на запити в яких вказано заповнити масив, або повернути значення, рушій повертає "основну" (англ. default) властивість об'єкта. Для фільмів - це назва. Для об'єктів що не мають назви - ідентифікатор. Якщо замість [], чи null запитати {} то нам повернуть словник в якому заповнені поля id, name та type. Ну, або ми можемо отримати потрібні нам поля за допомогою вкладеного запиту.

Предметна область - кінематограф

ред.

Загалом вивчення формату в якому представлена певна предметна область на Freebase полягає у вивченні типів. Для цього можна скористатись розділом Schema (наприклад схема для /film/film), чи скриптом з аналогічною функцією:

import freebase
query = {
  "id":   "/film/film",
  "type": "/type/type",
  "properties": [{
    "id": None,
    "name":          None,
    "expected_type": None,
    "/freebase/documented_object/tip": None
  }]
}

res = freebase.mqlread(query)
props = res['properties']

print "{|\n|-\n! Ідентифікатор !! Назва !! Очікуваний тип !! Довідка \n"
for p in props:
        print "|-\n| <code>%s</code> || <code>%s</code> || <code>%s</code> || %s" %( p['id'], p['name'], p['expected_type'], p['/freebase/documented_object/tip'])
print "|}"

Результат його роботи, з наступним ручним опрацюванням:

Ідентифікатор Назва Очікуваний тип Довідка
/film/film/initial_release_date Initial release date /type/datetime Прем'єра. (YYYY-MM-DD)
/film/film/directed_by Directed by /film/director Режисер
/film/film/produced_by Produced by /film/producer Продюсер
/film/film/written_by Screenplay by /film/writer Сценарист
/film/film/cinematography Cinematography /film/cinematographer Оператор
/film/film/edited_by Edited by /film/editor Монтаж
/film/film/music Music by /film/music_contributor Композитор
/film/film/language Languages /language/human_language Мови
/film/film/rating Rated /film/content_rating None
/film/film/estimated_budget Estimated budget /measurement_unit/dated_money_value Бюджет
/film/film/country Country of origin /location/country Країна
/film/film/starring Performances /film/performance В ролях
/film/film/runtime Runtime /film/film_cut Тривалість
/film/film/soundtrack Soundtrack /music/soundtrack None
/film/film/genre Genres /film/film_genre Жанри
/film/film/film_series Film Series /film/film_series None
/film/film/story_by Story by /film/film_story_contributor None
/film/film/sequel Sequel /film/film Сиквел
/film/film/prequel Prequel /film/film Приквел
/film/film/film_format Film format /film/film_format None
/film/film/costume_design_by Costume design by /film/film_costumer_designer Responsible for the overall design of costumes of the film, individual designs/gowns for a star should be listed in Other crew
/film/film/other_crew Other crew /film/film_crew_gig Used for all the other crew members of a film production that do not have an existing specifically named crew property (eg. gaffer, best boy, greens supervisor, make-up artist, etc.).
/film/film/trailers Trailers /common/webpage Трейлери
/film/film/distributors Distributors /film/film_film_distributor_relationship None
/film/film/other_film_companies Other film companies /film/film_film_company_relationship None
/film/film/production_companies Production companies /film/production_company None
/film/film/tagline Tagline /type/text Слоган
/film/film/release_date_s Release date(s) /film/film_regional_release_date Прем'єри в регіонах
/film/film/film_festivals Film festivals /film/film_festival_event Фестивалі на яких демонструвався фільм.
/film/film/nytimes_id NY Times ID /type/enumeration None
/film/film/featured_song Featured Song /film/film_featured_song Song(s) that were written specifically for the film (or pre-existing song(s) used for great effect within the film). This is meant for the notable (or so noted in the credits for pre-1980's films) and not every single song that is placed within a production (think of Casablanca's "As Time Goes By," Dirty Dancing's "We're Having the Time of Our Lives," Goldfinger's "Goldfinger," Mortal Kombat's "Theme From Mortal Kombat," and Ghostbuster's "Ghostbusters").
/film/film/metacritic_id Metacritic film ID /type/enumeration None
/film/film/apple_movietrailer_id Apple movie trailer ID /type/enumeration None
/film/film/rottentomatoes_id Rotten Tomatoes ID /type/enumeration None
/film/film/executive_produced_by Executive produced by /film/producer Виконавчий продюсер
/film/film/film_casting_director Casting director /film/film_casting_director The lead person(s) involved in casting of this film production
/film/film/film_production_design_by Production design by /film/film_production_designer The lead production designer(s) responsible on the film production
/film/film/film_art_direction_by Art direction by /film/film_art_director The art director(s) of this film production
/film/film/film_set_decoration_by Set Decoration by /film/film_set_designer Set designer for film production (assistants and other associated set designer roles are to be listed in Other crew)
/film/film/traileraddict_id Trailer Addict ID /type/enumeration None
/film/film/gross_revenue Gross revenue /measurement_unit/dated_money_value Касовий збір
/film/film/pre_production Pre-production /measurement_unit/time_interval The start and end dates of the film's pre-production (includes all phases of development for the film) before actual filming begins.
/film/film/filming Filming /measurement_unit/time_interval The start and end dates for the production (shooting film) phase of creating the the film.
/film/film/post_production Post-production /measurement_unit/time_interval The start and end dates for the post-production (effects, editing, scoring etc.).