Получение постов и комментариев с Facebook с помощью Python. Часть 2

В части 1 нашего урока мы стали разработчиками приложения Facebook и узнали, как делать авторизованные обращения к Graph API для получения простых данных постов и вывода их на консоль. Во второй части мы рассмотрим некоторые более сложные моменты работы с постами, например, как получить больше постов, чем возвращается на первой странице, или как найти лайки конкретного поста. Дополнительно мы расширим нашу базу данных так, чтоб мы могли собирать эти данные для дальнейшего анализа.

ПРЕДВАРИТЕЛЬНЫЕ ТРЕБОВАНИЯ:

Получение постов в зависимости от времени

В прошлой части для отображения в броузере данных постов мы оперировали следующим адресом:

https://graph.facebook.com/walmart/posts/?key=value&access_token=APP_ID|APP_SECRET

Когда мы смотрели на данные, которые отображаются при открытии данного адреса в броузере, то видели только наиболее свежие посты. Но поскольку мы хотим иметь данные для более глубокого и качественного анализа, то нам нужна возможность продвигаться дальше в прошлое. Для этого давайте откроем этот адрес в броузере и перейдем в самый низ.

Получение данных постов с Facebook

Как вы можете заметить тут есть элемент с ключем paging с подэлементами next и previous. Адрес, который нас интересует - это адрес в элементе с ключем next. Давайте добавим в наш скрипт еще одну функцию, которая поможет нам проходиться циклом по результатам и находить посты, которые нам нужны.

def scrape_posts_by_date(graph_url, date, post_data):
    #render URL to JSON
    page_posts = render_to_json(graph_url)

    #extract next page
    next_page = page_posts["paging"]["next"]

    #grab all posts
    page_posts = page_posts["data"]

    #boolean to tell us when to stop collecting
    collecting = True

    #for each post capture data
    for post in page_posts:
        #for each post capture data
    for post in page_posts:
        try:
            current_post = [post["id"], post["message"],
                            post["created_time"],
                            post["shares"]["count"]]

        except Exception:
            current_post = [ "error", "error", "error", "error"]

        if current_post[2] != "error":
            #compare dates
            if date <= current_post[2]:
                post_data.append(current_post)

            elif date > current_post[2]:
                print "Done collecting"
                collecting = False
                break

    #If we still don't meet date requirements, run on next page
    if collecting == True:
        scrape_posts_by_date(next_page, date, post_data)

    return post_data

Теперь давайте рассмотрим функцию строку за строкой. Из определения функции видно, что в нее передается три аргумента: graph_url, которая является стандартным адресом для получения наших постов. Далее идет дата date, которая определяет как далеко в прошлое мы должны зайти при сборе данных. И наконец в переменной post_data, в которой содержится список собранных нами постов.

В первом выражении мы просто берем наш адрес для получения постом и преобразуем его в объект JSON. Далее мы присваиваем переменной _next_url__ адрес, который соответствует следующей странице наших данных. Определять нажно ли нам переходить по этому адресу или нет, мы будет дальше в функции.

В следующем присваивании мы получаем содержимое постов из JSON и сохраняем его в переменной page_posts. Далее мы в цикле пройдемся по ним и получим интересующую нас информацию.

Далее мы создаем флаг, который будем использовать в цикле обработки данных для того, чтоб отметить окончание сбора данных и выхода из цикла. Мы выставляем начальное значение в True и переключим в False, когда нужно будет выйти из цикла.

Далее мы создаем цикл for для перебора постов. Внутри тела цикла мы используем блок try-except для перехвата всех ошибок, которые могут возниктуть в процессе обработки поста. В данном примере мы пытаемся получить ID поста, сообщение, время создания и количество репостов. Вы, естественно, можете убрать или добавить любые данные поста. В следующей части мы добавим “лайки”.

В следующем блоке кода идет условный оператор if, в котором опредаляется нужно ли нам запрашивать следующую страницу или нет. Первое условие проверяет не содержит ли текущий пост ошибок. В следующем условии мы сравниваем дату публикации поста с датой, переданной в функцию. Если дата поста больше чем дата, переданная функции, то мы выставляем значение нашего флага в False и прерываем цикл.

Наконец, в завершении функции, если наша переменная-флаг все еще равна True, то мы вызываем эту же функцию с теми же аргументами, но в качестве адреса запроса передаем адрес следующей страницы постов next_page. Такой вызов функции из самой себя называется рекурсией.

Теперь давайте перейдем в нашу основную функцию и добавим вызов только что рассмотренной функции. Так как мы используем даты в нашем скрипте, то сначала нам нужно подключить библиотеку работы с датами datetime. Ниже приведено, кок будет теперь выглядеть наш блок импорта:

import json
import mysql.connector
import datetime

Далее, в нашей главной функции сразе после формирования graph_url добавим следующие строки для формирования даты, до которой будет собирать посты. В данном выражении мы создаем дату, которую будем сравнивать с юникод строкой, получаемой из Graph API. В данном примере мы создаем дату на 1 неделю до сегоднешней.

last_crawl = datetime.datetime.now() - datetime.timedelta(weeks=1)
last_crawl = last_crawl.isoformat()

Теперь заменим строки, в которых мы получали информацию о посте, на вызов нашей функции:

#extract post data
post_url = create_post_url(current_page, APP_ID, APP_SECRET)

post_data = []
post_data = scrape_posts_by_date(post_url, last_crawl, post_data)

Здесь мы создаем простой список, в котором будут сохраняться данные наших постов. Затем мы вызываем нашу новую функцию и получаем данные в переменную post_data.

Получение Лайков постов

Как я упоминал выше, мы не получаем количество лайков при запросе списка постов из Graph API. Для того, чтобы получить количество лайков, нам нужно сделать дополнительный запрос к Graph API. Ниже приведен пример URL, что нам нужно будет создать для того, чтобы получить количество лайков:

https://graph.facebook.com/post_id/likes?summary=true&key=value&access_token=APP_ID|APP_SECRET

Попробуйте открыть данный адрес в браузере, подставив post_id, для которого вы хотите получить количество лайков. Если открыть URL в браузере и прокрутите вниз до конца страницы, то вы увидите общее количество лайков:

Получение количества лайков в JSON

Для того, чтоб сформировать подобный URL для поста, добавим в наш скрипт следующую функцию:

def get_likes_count(post_id, APP_ID, APP_SECRET):
    #create Graph API Call
    graph_url = "https://graph.facebook.com/"
    likes_args = post_id + "/likes?summary=true&key=value&access_token" + APP_ID + "|" + APP_SECRET
    likes_url = graph_url + likes_args
    likes_json = render_to_json(likes_url)

    #pick out the likes count
    count_likes = likes_json["summary"]["total_count"]

    return count_likes

Рассмотрим функцию подробнее. Мы передаем в нее ID поста post_id и наши app_id и APP_SECRET. Затем, подобно тому, как мы создавали post_url, мы берем наш стандартный graph_url и добавляем к нему несколько аргументов. Далее, мы преобразуем созданный URL в объект JSON. После того, как наш объект JSON был получен, мы получаем из него общее количество лайков и возвращаем его.

Теперь, когда у нас есть функция получения количество лайков для поста, включим эту функцию в функцию получения постов. Это позволит нам, получая в цикле информацию о постах, сразу получать и данные о лайках для них. Для этого в определении функции нужно добавить переменные app_id и APP_SECRET, которые будут переданы в функцию get_likes_count, зависящую от них.

def scrape_posts_by_date(graph_url, date, post_data, APP_ID, APP_SECRET):

Далее в теле цикла добавим функцию, которая получает количество лайков, и скорректируем наш список данных.

#for each post capture data
for post in page_posts:
    try:
        likes_count = get_likes_count(post["id"], APP_ID, APP_SECRET)
        current_post = [post["id"], post["message"], likes_count,
                        post["created_time"], post["shares"]["count"]]

    except Exception:
        current_post = [ "error", "error", "error", "error"]

    if current_post[3] != "error":
        print date
        print current_post[3]
        if date <= current_post[3]:
            post_data.append(current_post)

        elif date > current_post[3]:
            print "Done collecting"
            collecting = False
            break

Наконец, нужно также добавить новые аргументы в вызов рекурсивной функции. Таким образом окончание тела функции должно иметь вид:

#If we still don't meet date requirements, run on next page
if collecting == True:
    scrape_posts_by_date(next_page, date, post_data, APP_ID, APP_SECRET)

return post_data

Добавление таблицы постов

Хорошо, теперь, когда у нас есть возможность получать данные постов, давайте создадим таблицу для их хранения. Для этого выполним следующий код на нашей facebook_data базе данных.

USE facebook_data;

CREATE TABLE post_info(
    id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
    fb_post_id VARCHAR(200),
    message VARCHAR(800),
    likes_count BIGINT UNSIGNED,
    time_created DATETIME,
    shares BIGINT UNSIGNED,
    page_id INT NOT NULL,
    INDEX(page_id),
    FOREIGN KEY(page_id)
        REFERENCES page_info(id)
        ON DELETE CASCADE
) ENGINE INNODB;

В этом коде создается таблица с именем post_info. В ней создается первичный ключ, который является автоинкрементным и будет увеличиваться каждый раз, когда мы сохраняем пост. Далее, для каждого информационного параметра поста создается соответствующая колонка. И, наконец, создается page_id. Этот идентификатор соответствует идентификатору, который создается в таблице page_info при каждом сохранении информации страницы. Затем мы устанавливаем page_id в качестве внешнего ключа.

Сохранение данных поста

Теперь добавим несколько строк, сохраняюших данные полученных постов. После запроса, который мы писали для сохранения данных страницы, добавьте присвоение:

#SQL statement for adding post data
insert_posts = ("INSERT INTO post_info "
                "(fb_post_id, message, likes_count, time_created, shares, page_id)"
                "VALUES (%s, %s, %s, %s, %s, %s)")

Подобно тому, как сохраняются данные страницы, тут просто данные, получаемые в качестве переменных, подставляются в запрос и вставляются в таблицу базы данных.

Так как, скорее всего у нас будет много постов для каждой старницы, нам нужно создать цикл для передачи данных в запрос. При этом обратите внимание, что в запросе требуется 6 переменных, тогда как в списке мы имеем только 5. Нам нужно добавить внешний ключ для связи страницы с нашими данными. Добавьте следующий блок кода в основной функции скрипта ниже сохранения данных страницы:

# grab primary key
last_key = cursor.lastrowid

# loop through and insert data
for post in post_data:
    post.append(last_key)
    cursor.execute(insert_posts, post)

Теперь мы можем запустить наш скрипт и сохранить в базу данных информацию о страницах и постах.

Наш код на данный момент

Мы рассмотрели многое в данном уроке и на данный момент код нашего скрипта должен иметь следующий вид:

import urllib2
import json
import mysql.connector
import datetime

def connect_db():
    #fill this out with your db connection info
    connection = mysql.connector.connect(user='JohnDoe', password='abc123',
                                         host = '127.0.0.1',
                                         database='facebook_data')
    return connection

def create_post_url(graph_url, APP_ID, APP_SECRET):
    #create authenticated post URL
    post_args = "/posts/?key=value&access_token=" + APP_ID + "|" + APP_SECRET
    post_url = graph_url + post_args

    return post_url

def render_to_json(graph_url):
    #render graph url call to JSON
    web_response = urllib2.urlopen(graph_url)
    readable_page = web_response.read()
    json_data = json.loads(readable_page)

    return json_data

def scrape_posts_by_date(graph_url, date, post_data, APP_ID, APP_SECRET):
    #render URL to JSON
    page_posts = render_to_json(graph_url)

    #extract next page
    next_page = page_posts["paging"]["next"]

    #grab all posts
    page_posts = page_posts["data"]

    #boolean to tell us when to stop collecting
    collecting = True

    #for each post capture data
    for post in page_posts:
        try:
            likes_count = get_likes_count(post["id"], APP_ID, APP_SECRET)
            current_post = [post["id"], post["message"], likes_count,
                            post["created_time"], post["shares"]["count"]]

        except Exception:
            current_post = [ "error", "error", "error", "error"]

        if current_post[3] != "error":
            print date
            print current_post[3]
            if date <= current_post[3]:
                post_data.append(current_post)

            elif date > current_post[3]:
                print "Done collecting"
                collecting = False
                break


    #If we still don't meet date requirements, run on next page
    if collecting == True:
        scrape_posts_by_date(next_page, date, post_data, APP_ID, APP_SECRET)

    return post_data

def get_likes_count(post_id, APP_ID, APP_SECRET):
    #create Graph API Call
    graph_url = "https://graph.facebook.com/"
    likes_args = post_id + "/likes?summary=true&key=value&access_token" + APP_ID + "|" + APP_SECRET
    likes_url = graph_url + likes_args
    likes_json = render_to_json(likes_url)

    #pick out the likes count
    count_likes = likes_json["summary"]["total_count"]

    return count_likes

def main():
    #simple data pull App Secret and App ID
    APP_SECRET = "Your APP SECRET"
    APP_ID = "Your APP ID"

    #to find go to page's FB page, at the end of URL find username
    #e.g. http://facebook.com/walmart, walmart is the username
    list_companies = ["walmart", "cisco", "pepsi", "facebook"]
    graph_url = "https://graph.facebook.com/"

    #the time of last weeks crawl
    last_crawl = datetime.datetime.now() - datetime.timedelta(weeks=1)
    last_crawl = last_crawl.isoformat()

    #create db connection
    connection = connect_db()
    cursor = connection.cursor()

    #SQL statement for adding Facebook page data to database
    insert_info = ("INSERT INTO page_info "
                   "(fb_id, likes, talking_about, username)"
                   "VALUES (%s, %s, %s, %s)")

    #SQL statement for adding post data
    insert_posts = ("INSERT INTO post_info "
                    "(fb_post_id, message, likes_count, time_created, shares, page_id)"
                    "VALUES (%s, %s, %s, %s, %s, %s)")


    for company in list_companies:
        #make graph api url with company username
        current_page = graph_url + company

        #open public page in facebook graph api
        json_fbpage = render_to_json(current_page)


        #gather our page level JSON Data
        page_data = [json_fbpage["id"], json_fbpage["likes"],
                     json_fbpage["talking_about_count"],
                     json_fbpage["username"]]
        print page_data

        #extract post data
        post_url = create_post_url(current_page, APP_ID, APP_SECRET)

        post_data = []
        post_data = scrape_posts_by_date(post_url, last_crawl, post_data, APP_ID, APP_SECRET)



        print post_data

        #insert the data we pulled into db
        cursor.execute(insert_info, page_data)

        #grab primary key
        last_key = cursor.lastrowid

        #loop through and insert data
        for post in post_data:
            post.append(last_key)
            cursor.execute(insert_posts, post)

        #commit the data to the db
        connection.commit()

    connection.close()

if __name__ == "__main__":
    main()

Source: Harvesting Facebook Posts and Comments with Python

 
comments powered by Disqus