Большие выводы на маленьких выборках

Чтобы начать двигать недвижимость, достаточно сменить систему отсчёта.
Фейсбук
→ Посты за 2020, 2019, 2018, 2017, 2016

Автоматизация «чистки» площадок в РСЯ

Используем API Директа и питон-скрипт. Понадобится access_token для работы с API Директа. Если его нет, смотрите как получить в конце прошлой статьи.

Готовим правило для исключения площадок

Есть разные подходы к тому, как принимать решения об отключении конкретной площадки. Я сделаю на примере слишком высокого CTR. Будем считать «плохими» все площадки CTR которых выше заданного значения. Скрипт легко адаптировать для поиска площадок со слишком высоким CPA по всем или по конкретной цели из Яндекс.Метрики.

Импорты

import requests
import json

import pandas as pd
import numpy as np

from datetime import datetime, timedelta
from time import sleep

import re

import warnings
warnings.filterwarnings('ignore')

Токен, логин и заголовки запроса

Если аккаунт агентский, добавьте «Use-Operator-Units»: «true», чтобы использовать баллы агентства.

access_token = '%ваш-токен%'
login = '%ваш-логин%'

headers = {
            "Authorization": f'Bearer {access_token}',
           "Client-Login": login,
           "Accept-Language": "ru",
           "processingMode": "auto",
           "returnMoneyInMicros": "false",
           "skipReportHeader": "true",
           "skipReportSummary": "true",
            "Use-Operator-Units": "true" # Использовать баллы API агентства
           }

Получаем данные о кампаниях

url = 'https://api.direct.yandex.com/json/v5/campaigns'
body = {
  "method": "get",
  "params": { 
    "SelectionCriteria": {  
      "Types": ["TEXT_CAMPAIGN"],
      "Statuses": ["ACCEPTED"],
    }, 
    "FieldNames": ["Id" , "Name", "State", "Status"], 
    }
  }
res = requests.post(url, headers=headers, json=body)

Формируем dataframe

camps = {}
for c in res.json()['result']['Campaigns']:
    for key in c:
        if key not in camps:
            camps[key] = []
            camps[key].append(c[key])
        else:
            camps[key].append(c[key])
    
df = pd.DataFrame(camps)
data = df.copy()
data
Получили таблицу с кампаниями, их ID, состоянием и статусом

Далее, надо оставить только РСЯ кампании — обычно специалисты по контекстной рекламе рекламе называют их особым образом — например пишут в разваниях _rsya, _net, _network, _context или как-то ещё — фильтруем кампании по названию и получаем лист с id сетевых кампаний.

network_campaigns_nametag_string = '_net_'
net_camps = data[data.Name.str.contains(network_campaigns_nametag_string)]['Id'].tolist()

Получаем статистику за 90 дней

Если указать значения в «Goals» : [XXXXX, YYYYY], получим кол-во конверсий именно по этим целям. Если не указывать, получим кол-во конверсий по всем целям (достижение любой цели).

Если нужен другой период, можно использовать шаблоны или пользовательские значения.

body = {
    "params": {
        "SelectionCriteria": {
            
"Filter": [{
        "Field": "CampaignId",
        "Operator": "IN",
        "Values": net_camps
      } ]            

        },
        "FieldNames": [
#             "Date",
            "CampaignName",
            "CampaignId",
            "ExternalNetworkName",
            "Placement",
#             "AdNetworkType",
#             "CampaignType",


            "Impressions",
            "Clicks",
            "Cost",
            "Bounces",
            "Conversions"
        ],
#           "Goals" : [66852307, 88594366], # Идентификаторы целей в Метрике, если не указывать — будет один столбец с общим кол-вом конверсий
        "ReportName": str(datetime.now()),
        "ReportType": "CAMPAIGN_PERFORMANCE_REPORT",
        "DateRangeType": "LAST_90_DAYS", # Период отчёта
        "Format": "TSV",
        "IncludeVAT": "YES", # Учитывать НДС
        "IncludeDiscount": "NO"
    }
}

url = 'https://api.direct.yandex.com/json/v5/reports'    

print(login)
status = None
while status in {201, 202, None}:
    res = requests.post(url, headers=headers, json=body)
    status = res.status_code
    retryIn = res.headers.get('retryIn', None)
    reportsInQueue = res.headers.get('reportsInQueue', None)
    print(f'status = {status} wait {retryIn}. queue {reportsInQueue}')
    if retryIn:
        sleep(int(retryIn))

Тут возможно придется немного подождать. Далее разбираем ответ и формируем dataframe:

lists = []
for line in res.text.split('\n'):
    if len(line.split('\t')) > 1:
        lists.append([login] + line.split('\t'))   

stat_dict = {}
n = 1
for line in lists:
    if n == 1:                  # Первую строку записываем ключами словаря   
        for col in line:
            stat_dict[col] = []
    else:                    #   Другие строки записываем как значения ключей словаря
        for y in range(0,len(line)):
            stat_dict[list(stat_dict.keys())[y]].append(line[y])        
    n += 1
        
yd = pd.DataFrame(stat_dict)
yd = yd.sort_values("Cost", ascending = 0)

yd['Impressions'] = yd['Impressions'].astype(int)
yd['Clicks'] = yd['Clicks'].astype(int)
yd['Cost'] = yd['Cost'].astype(float)

# Поправим значения в столбцах с конверсиями
for col in [x for x in yd.columns if 'Conversions' in x]:
    yd[col] = yd[col].map(lambda x: 0 if x == '--' else x).astype(int)

        
yd

Группируем статистику по площадкам:

find_bad = yd[yd['Placement'] != '--'].iloc[:,4:].groupby('Placement')\
.sum().reset_index().sort_values(by='Cost', ascending=0)


find_bad['CPC'] = find_bad['Cost']/find_bad['Clicks']
find_bad['CTR'] = find_bad['Clicks']/find_bad['Impressions']
find_bad = find_bad[find_bad['Clicks'] > 0]
find_bad

Получаем табличку:

Здесь можно рассчитать и другие важные для вас метрики — cr или cpa для нужных целей

Фильтруем

Получим площадки с CTR выше 1,5%. Можно задать комбинации фильтров, например CTR выше 2%, показов более 100 и т. п.

too_high_ctr = 0.015
find_bad_CTR = find_bad[find_bad['CTR'] > too_high_ctr]

#  Сортируем по убыванию расходов
bb = find_bad_CTR[['Placement', 'Cost']].sort_values(by='Cost', ascending = 0)
BADS = bb['Placement'].tolist()
print(BADS)
Получили блеклист площадок для исключения

Добавляем к текущим кампаниям

Функция принимает четыре аргумента: логин, токен, метка РСЯ-кампаний и блеклист.

Если площадки в новом блеклисте уже исключены — они не добавятся.

Если существующий блеклист и новый в сумме дают более 1000 площадок — текущие остаются, из нового добавится такое кол-во площадок, чтобы в сумме было 1000 (приоритет самым затратным).

def addNewExcludedSites(login, access_token, network_campaigns_nametag_string, new_excluded_sites_list):

    YandexWhiteList = []
    
    AlwaysOn = ['m.yandex.ru', 'yandex.ru', 'yandex.by', 'm.yandex.by', 'yandex.kz', 'yandex.ua', 'm.yandex.kz', 'm.yandex.ua']
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Client-Login": login,
        "Accept-Language": "ru",
        "processingMode": "auto",
        "returnMoneyInMicros": "false",
        "skipReportHeader": "true",
        "skipReportSummary": "true",
        "Use-Operator-Units": "true"
               }

    # Получаем список включенных, остановленных или закончившихся кампаний у которых в названии есть network_campaigns_nametag_string
    url = 'https://api.direct.yandex.com/json/v5/campaigns'
    
    body = {
      "method": "get",
      "params": { 
        "SelectionCriteria": {  
          "Types": ["TEXT_CAMPAIGN"],
          "States": [ "ON", "SUSPENDED", "ENDED"],
          "Statuses": ["ACCEPTED"],
        }, 
        "FieldNames": ["Id" , "Name", "State", "Status", "ExcludedSites"], 
        }
      }

    res = requests.post(url, headers=headers, json=body)

    camps = {}
    for c in res.json()['result']['Campaigns']:
        for key in c:
            if key not in camps:
                camps[key] = []
                camps[key].append(c[key])
            else:
                camps[key].append(c[key])

    df = pd.DataFrame(camps)
    data = df.copy()

    # Для РСЯ-кампаний добавляем к текущему блеклисту новые площадки
    data = data[data.Name.str.contains(network_campaigns_nametag_string)] #  Оставляем кампании с нужными названиями
    data['ExcludedSites'] = data['ExcludedSites'].map(lambda x: x['Items'])
    
    
    # Добавляем новый блеклист к текущему
    
    campaigns_to_update = ', '.join(data['Name'].tolist())
    print(f"Campaigns to update: {campaigns_to_update}\n")  
    
    for i in data['Id'].tolist():
        
        # Название кампании
        CampaignName = data[data['Id'] == i]['Name'].tolist()[0]

        # Получим актуальный блеклист кампании
        ExcludedSites = sorted(data[data['Id'] == i]['ExcludedSites'].tolist()[0])
        
        # Удалим из нового списка площадки, которые уже есть в блеклисте кампании
        BadList_New = [x for x in new_excluded_sites_list if x not in ExcludedSites]
        
        # Удалим из нового списка площадки, которые Яндекс не даёт отключить — AlwaysOn
        BadList_New = [x for x in BadList_New if x not in AlwaysOn]

        # Если площадки из нового блеклиста уже отключены — ничего не делаем
        if len(BadList_New) == 0:
            print(f"{CampaignName} (ID:{i}), Allready in list! ExcludedSites list size: {len(ExcludedSites)}\n")
            continue

        else:
            print(f"{CampaignName} (ID:{i}), ExcludedSites list size: {len(ExcludedSites)}")
            
            # Максимальное число минус-площадок = 1000. 
            # Текущий список не трогаем, новый добавляем так, чтобы в сумме было 1000.
            # Если новый список на основании статистики, приоритет минус-площадкам с бо́льшими расходами.
            LE = len(list(set(ExcludedSites))) # Размер текущего списка
            MAX_NEW_LIST_LEN = 1000-LE # Сколько максимально можно добавить
            BadList_New = BadList_New[:MAX_NEW_LIST_LEN] # Берём нужное кол-во
            
            url = 'https://api.direct.yandex.com/json/v5/campaigns'
            body = {
              "method": "update",
              "params": { 
                "Campaigns": [{  
                  "Id": i,
                 "ExcludedSites": {  
                    "Items": sorted(list(set(ExcludedSites + BadList_New)))
                         },        
                  } 
                ] 
              }
            }

            res = requests.post(url, headers=headers, json=body)
            
            if 'Errors' not in res.json()['result']['UpdateResults'][0]:
                print(f"Added {BadList_New} to ExcludedSites.\nNew ExcludedSites list size: {len(ExcludedSites) + len(BadList_New)}\n")   

                # Список «проверенных» площадок Яндекса
                # https://yandex.ru/adv/news/vklyuchenie-pokazov-reklamy-na-ploschadkakh-rsya-s-proverennym-kachestvom-trafika?fbclid=IwAR1aUWLfWJiFr2XuzdRyhMwmWIwJEHsFbuj0ciQEdVF95eQwFdqAF5_pdiw
                try:
                    for i in res.json()['result']['UpdateResults'][0]['Warnings']:
                        i = re.sub('Элемент\s(.+?)\sсписка.+', r'\1', i['Details'])
                        if i not in YandexWhiteList:
                            YandexWhiteList.append(i)
                except:
                    continue


            else:
                print(f"Error trying update ExcludedSites for {CampaignName} (ID:{i})")
                for e in res.json()['result']['UpdateResults'][0]['Errors']:
                    print (e['Details'])
                    
                continue

    if len(YandexWhiteList) > 0:      
        print('Неотключаемые площадки РСЯ:')
        for y in sorted(YandexWhiteList):
            print(y)

bad_list = BADS
addNewExcludedSites(login, access_token, network_campaigns_nametag_string, bad_list)
В интерфейсе Директа можно посмотреть изменения.

Ссылки
Документация API Директа

P. S. Неотключаемые площадки РСЯ
В конце выполнения фукнции печатается YandexWhiteList — те самые площадки с «проверенным» трафиком Яндекса.

Предупреждение, что эффекта не будет

afisha.yandex.ru
collections.yandex.ru
com.android.browser
com.edadeal.android
com.s-g-i.edadeal
com.yandex.browser
com.yandex.launcher
com.yandex.mobile.realty
com.yandex.zen
disk.yandex.ru
dsp.yandex.ru
fotki.yandex.ru
game.yandex.ru
images.yandex.by
images.yandex.com
images.yandex.kz
images.yandex.ru
images.yandex.ua
kinopoisk.ru
m.afisha.yandex.ru
m.collections.yandex.ru
m.edadeal.ru
m.games.yandex.ru
m.images.yandex.by
m.images.yandex.ru
m.local.yandex.ru
m.pogoda.yandex.ru
m.rasp.yandex.ru
m.sport.yandex.ru
m.thequestion.ru
m.tv.yandex.ru
m.video.yandex.ru
m.zen.yandex.com
m.znatoki.yandex.ru
maps.yandex.ru
metro.yandex.ru
music.yandex.ru
pogoda.yandex.ru
ru.auto.ara
ru.yandex.disk
ru.yandex.mobile
ru.yandex.mobile.search
ru.yandex.mobile.transport
ru.yandex.mobile.weather
ru.yandex.rasp
ru.yandex.searchplugin
ru.yandex.searchplugin.beta
ru.yandex.weatherplugin
ru.yandex.yandexbus
ru.yandex.yandexmaps
sport.yandex.ru
tv.yandex.ru
video.yandex.ru
zen.yandex.com
zen.yandex.ru

27 марта  

Питон-скрипт для проверки опечаток на сайте

Опечатки бывают у всех ;-) Пик
Крост
Эталон
VALO

В дополнение к скрипту для поиска опечаток в объявлениях Директа проверяем сайт на опечатки.
Скрипт использует библиотеку Selenium для парсинга страницы и Яндекс Спеллер для проверки орфографии.

В коде замените путь до хромдрайвера:

browser = webdriver.Chrome(options=options, executable_path=r'/Users/vitalybakhvalov/Documents/chromedriver')

И url страницы для проверки:

check_url = 'http://italylov.ru/blog'

Скачать

UPD1
Вторая версия — находит на заданной странице ссылки на другие страницы сайта и проверяет все. Удобно когда страниц много..

Проверка объявлений Директа на опечатки

Опечатки случаются, и почему-то клиенты всегда успевают находить их раньше, чем авторы.
Для таких случаев сделал py-скрипт для поиска опечаток с помощью Яндекс Спеллера и API Директа. Не панацея, так как не видит пунктуацию, но орфографию и опечатки находит. Лучше пошагово запустить в Юпитере.

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

Понадобится access_token для работы с API Директа. Если его нет, смотрите как получить в конце статьи.

Сначала получим список активных кампаний.

url = 'https://api.direct.yandex.com/json/v5/campaigns'

Затем получим все активные объявления и вытащим тексты.

url = 'https://api.direct.yandex.com/json/v5/ads'

Найдем уникальные заголовки, вторые заголовки и тексты, разделим по словам и каждое прогоним через Спеллер.

Повторим упражнение для заголовков и описаний быстрых ссылок.

url = 'https://api.direct.yandex.com/json/v5/sitelinks'

И для уточнений.

url = 'https://api.direct.yandex.com/json/v5/adextensions'

Скриншоты

Ошибки собираются в словарь. Count — сколько раз встречалось. Variants — предложения Спеллера, как правильно. Ложноположительные результаты можно добавить в ok-list, чтобы они не выводились как ошибки
Датафрейм с быстрыми ссылками
Ещё ошибочки

UPD1

В проверке быстрых ссылок заменил body запроса

body = {
      "method": "get",
      "params": { 
        "SelectionCriteria": { 
                
        "Ids" : [int(x) for x in SitelinkSetIds if x != ''] # вот тут бывали ошибки если взять просто SitelinkSetIds


        }, 
        "FieldNames" : [ "Id" ],
 "SitelinkFieldNames": [ "Title" , "Href" , "Description"]


      } 
    }

UPD2

Если аккаунт прямой, а не агентский поправьте в headers:

headers = { 
    'Authorization': f'Bearer {access_token}', 
    'Client-Login' : login,
    "Accept-Language": "ru",
    "skipReportHeader": "true",
    "skipReportSummary": "true",
#     "Use-Operator-Units": "true"  # Использование api-баллов агентства
}

UPD3

Объявления можно не фильтровать по только активным, а проверить все.

ads_df = ads_df[ads_df['State'] == 'ON'][['Id','TextAd','CampaignId']] # Только активные объявления
# ads_df = ads_df[['Id','TextAd','CampaignId']] # Все
print(f'Кол-во активных объявлений: {len(ads_df)}')

UPD4 Получение токена

Вы зарегистрировали приложение. У вас есть ID и пароль.

#https://oauth.yandex.ru/

appid =  'ваш-ID'
apppw =  'ваш-пароль'
appcallback =  'https://oauth.yandex.ru/verification_code'
link = f'https://oauth.yandex.ru/authorize?response_type=code&client_id={appid}'
print(link)

Переходите по ссылке под вашим логином и копируете код.

code = 'ваш-код'
# Получаем токен
code_request = f'https://oauth.yandex.ru/authorize'
    
code_req = {
    'grant_type': 'authorization_code',
    'code': code,
    'client_id': appid,
    'client_secret': apppw
}

token_responce = requests.post('https://oauth.yandex.ru/token', data = code_req)
if token_responce.status_code != 200:
    raise Exception('Wrong status code')

token_resp_data = token_responce.json()
access_token = token_resp_data['access_token']
print(access_token)

Медленный парсер подсказок Яндекса (зато бесплатный)

Для тех, кого забанили в гугле и кейколекторе.

Берёт запросы из txt-файла, вбивает в Яндекс и собирает подсказки.

Как сделать

Скачайте скрипт .ipynb и создайте txt-файл с запросами. Сохраните в отдельную папку.

Будем использовать специальный дистрибутив Питона, который называется «Анаконда». Качаем версию Питона 3.6, устанавливаем, запускаем Anaconda Navigator, в нём жмем launch Jupiter Notebook.

Выберите папку со скачанными файлами, откройте файл .ipynb.

Жмём Run ALL из меню

Когда скрипт закончит работу — в папке появится новый текстовый файлик с подсказками.

P. S. ChromeDriver нужно устанавливать. Путь в скрипте указывается в строчке browser = webdriver.Chrome(chrome_options=options, executable_path=r’/Users/ПОЛЬЗОВАТЕЛЬ/Documents/chromedriver’)
Скачать драйвер

2019  

Парсим объявления в Директе-2 и делаем скриншоты

Скрипт берёт файл со списком запросов, вот такой, например:

Каждый запрос вбивает в Яндекс и записывает заголовки, тексты и отображаемые ссылки рекламных объявлений в CSV-файл + делает нотариально заверенные скриншоты выдачи каждого запроса и сохраняет в папке со скриптом — полезно для упоротых клиентов.

CSV-файл
Скриншоты в папке

Как сделать

Скачайте скрипт и файл с запросами. Сохраните в отдельную папку.

Будем использовать специальный дистрибутив Питона, который называется «Анаконда». Качаем версию Питона 3.6, устанавливаем, запускаем Anaconda Navigator, в нём жмем launch Jupiter Notebook.

Там выберите папку со скачанными файлами и файл .ipynb:

Задайте регион. Наример, Москва и Московская область — 1, а Санкт-Петербург — 2.

region = '1'

Жмём Shift + Enter (или Run из меню) для каждой ячейки — фишка Jupiter Notebook в том, что код выполняется пошагово. Когда дойдете до шага «Основной цикл» — придется подождать. Скрипт будет писать какой запрос он сейчас обрабатывает:

https://www.yandex.ru/search/ads?text=дилеры%20ауди%20+в%20москве&lr=1&p=0
Запрос 1 из 6
...
...
...
https://www.yandex.ru/search/ads?text=ауди%20официальный%20дилер%20+в%20москве&lr=1&p=0
Запрос 2 из 6
...
...
...
https://www.yandex.ru/search/ads?text=дилер%20ауди%20север%20москва&lr=1&p=0
Запрос 3 из 6
...
...
...
https://www.yandex.ru/search/ads?text=ауди%20север%20официальный%20дилер%20+в%20москве&lr=1&p=0
Запрос 4 из 6
...
...
...
https://www.yandex.ru/search/ads?text=купить%20ауди%20москва%20дилер&lr=1&p=0
Запрос 5 из 6
...
...
...
https://www.yandex.ru/search/ads?text=купить%20ауди%20+у%20официального%20дилера%20+в%20москве&lr=1&p=0
Запрос 6 из 6
...
...
...

Далее формируем датафрейм и записываем в CSV-файл. Как всё будет готово, появится табличка с первыми пятью строками результата и файл в папке со скриптом.

Первые пять строчек для первого запроса из файла. Все данные — в CSV-файле.

P. S. ChromeDriver нужно устанавливать. Путь в скрипте указывается в строчке browser = webdriver.Chrome(chrome_options=options, executable_path=r’/Users/vitalybakhvalov/Documents/chromedriver’)
Скачать драйвер

2019  

Как склеить выгрузку из колтрекинга с данными о визитах в гугл-таблицах

Пришло время, и вы вдруг начали подозревать, что ваш любимый канал programatic-cpa-want-exchange, хоть и помогает выполнить kpi по звонкам, но к визитам и сделкам не приводит.

Поскольку crm у вас ещё в стадии «почти допилили», то проверку придется делать руками: необходимо «склеить» данные о визитах с колтрекингом. Тут вас обычно ждут две проблемы: кривые телефонные номера и несколько телефонов у одного клиента.

Пример выгрузки из CRM с контактами тех, кто приехал в офис продаж

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

Приводим номера телефонов в единый вид

Удалим все не-цифры, возьмем 10 цифр справа и добавим к ним «7»
=REGEXREPLACE(REGEXREPLACE(ТЕКСТ(F9;"#");"\D";"");".*(\d{10})$";"7$1")

Избавляемся от столбцов

Конечно, проще сделать split в SQL, но иногда так быстрее
=QUERY({D18:E21\F18:F21;D18:E21\G18:G21};"select Col1,Col2,Col3 where Col3 != '' order by Col1 asc, Col2 asc label Col1 'id', Col2 'name', Col3 'phone'";0)

Итого

Теперь эти данные можно корректно подтянуть к выгрузке колтрекинга с помощью VLOOKUP/ВПР.
Гугл-таблица с примером.

2019  
2019   python

Подсчёт количества символов для Директа в Гугл-таблицах

При подсчёте количества символов в текстах объявлений Директа не учитываются некоторые символы.
Для верного подсчёта в Гугл-таблицах подойдет формула:

=ДЛСТР(REGEXREPLACE(REGEXREPLACE(A1;СИМВОЛ(34);"");"[«»,.:;!]";""))

Где A1 — ячейка с текстом.

2019   google_sheets

Big Query → DataFrame

Простой способ достать данные из Google Big Query в Jupyter Notebook.

# Установите библиотеки
# !pip install python-gflags
# !pip install httplib2
# !pip install google-api-python-client
# !pip install pandas-gbq

# Включите API: https://console.developers.google.com/apis/dashboard


import pandas as pd
from pandas.io import gbq

#  Ваш запрос в BQ
Query = """
SELECT
 *
FROM 
[your_dataset_name]
"""

# Укажите project_id и диалект
data = gbq.read_gbq(Query, project_id='your_BQ_project_id', dialect = 'standard')


data.head()

Источник

2018   big query   pandas   python

Начинаем работать с Google Sheets API v4

Статья будет полезна тем, кто хочет создавать Гугл-таблицы, вносить или забирать данные через API.

Может пригодиться для аналитики или автоматизации рутины: например, создания типовых документов. Продвинутым тру-программистам лучше не читать — эта запись своего рода конспект собственных экспериментов, чтобы ничего не забыть. Надеюсь, новичкам вроде меня с её помощью будет легче разобраться.

Понадобится anaconda, jupyter notebook и python 3.

Создаём аккаунт и авторизируемся

Нужна почта на gmail.
Заходим на Google Developers Console и создаём новый проект.
Включаем Drive API и Sheets API.
Сохраняем учётные данные и закрытый ключ:

После всех манипуляций вы скачаете json-файл. Он понадобится вот тут:

!pip install --upgrade google-api-python-client

from datetime import datetime
import calendar
import locale
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8') 
from pprint import pprint
import httplib2
import apiclient.discovery
from oauth2client.service_account import ServiceAccountCredentials
CREDENTIALS_FILE = 'XXX.json'  #  ← имя скаченного файла с закрытым ключом
credentials = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, ['https://www.googleapis.com/auth/spreadsheets',                                                                               'https://www.googleapis.com/auth/drive'])
httpAuth = credentials.authorize(httplib2.Http())
service = apiclient.discovery.build('sheets', 'v4', http = httpAuth)

Достаём данные

Создайте новый Googlesheet, дайте разрешение на редактирование всем у кого есть ссылка:

Скопируйте id из адресной строки:

Заполните ячейку A1 чем-нибудь неожиданным:

И попробуйте что-то вроде:

spreadsheetId = '1vR7sByPDE10wZSyz7I8hqBS8j5DgTNerFH2X_1sbVu8'
range_name = 'Лист1!A:G'
table = service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=range_name).execute()
table

Ура, забрали данные:

Пусть в исходной табличке будет табличка вида:

Ставим pandas, делаем dataframe:

import pandas as pd

campaign_list = []
visits_list = []
cost_list= []

for i in table['values'][1:]:

    campaign_list.append( i[0] )
    visits_list.append( i[1] )
    cost_list.append( i[2] )

right = pd.DataFrame( list(zip(campaign_list, visits_list, cost_list) ),columns=[ 
                                                table['values'][0][0],
                                                table['values'][0][1],
                                                table['values'][0][2]])
right

Вуаля ;-)

Отправляем данные

Создаём новый spreadsheet:

title = 'Hello, world'
list_title = '1'

spreadsheet = service.spreadsheets().create(body = {
    'properties': {'title': title, 'locale': 'ru_RU'},
    'sheets': [{'properties': {'sheetType': 'GRID',
                               'sheetId': 0,
                               'title': list_title,
                               'gridProperties': {'hideGridlines': True, 'rowCount': 5, 'columnCount': 5}}}]
}).execute()

Файл создан — смотрим, что получилось:

Если перейти по ссылке, увидим «Доступ закрыт» — всё верно, файл принадлежит сервисному аккаунту. Даём себе разрешение:

email = 'xxx@gmail.com' # ← ваша почта

driveService = apiclient.discovery.build('drive', 'v3', http = httpAuth)
shareRes = driveService.permissions().create(
    fileId = spreadsheet['spreadsheetId'],
    #body = {'type': 'anyone', 'role': 'writer'},  # доступ на редактирование кому угодно
    body = {'type': 'user', 'role': 'writer', 'emailAddress': email},
    fields = 'id'
).execute()

Теперь ссылка открывается:

Вставляем значение в выбранную ячейку:

cell = 'A1'
value = '1,618'

service.spreadsheets().values().batchUpdate(spreadsheetId = spreadsheet['spreadsheetId'], 
body = {
"valueInputOption": "USER_ENTERED",
"data": [
    {"range": list_title + "!" + cell,
     "majorDimension": "ROWS",
     "values": [[value]]}

]
}).execute()

Жирный шрифт:

DATA = {'requests': [{'repeatCell':
        {'range': 
            { 
              'startRowIndex': 0, # номер строки (нумерация с 0) с которой включительно будет применено форматирование
              'endRowIndex': 1, # номер строки до которой будет применено форматирование, не включительно
                
              'startColumnIndex': 0, # номер столбца (нумерация с 0)…
              'endColumnIndex': 1
            },
        
        'cell':  {'userEnteredFormat': {'textFormat': {'bold': True}}},

        'fields': 'userEnteredFormat', # другие параметры форматирования ячейки будут сброшены
    }}
]}

service.spreadsheets().batchUpdate(spreadsheetId = spreadsheet['spreadsheetId'], body=DATA).execute()

Число с тремя знаками после запятой, горизонтальное выравнивание влево, вертикальное по верху, 9-й шрифт, отменить жирность, перенос по словам:

DATA = {'requests': [{'repeatCell':
        {'range': 
            { 
              'startRowIndex': 0, # номер строки (нумерация с 0) с которой включительно будет применено форматирование
              'endRowIndex': 1, # номер строки до которой будет применено форматирование, не включительно
                
              'startColumnIndex': 0, # номер столбца (нумерация с 0)…
              'endColumnIndex': 1
            },
        
        'cell':  {'userEnteredFormat': {"numberFormat": { "type": "NUMBER", "pattern": "#,##0.000"}, 
                                        "verticalAlignment": "TOP", 
                                        "horizontalAlignment": "LEFT", 
                                        "wrapStrategy": "WRAP", 
                                        "textFormat": {"fontSize": 9,'bold': False}
                                              }},

        'fields': 'userEnteredFormat', # другие параметры форматирования ячейки будут сброшены
    }}
]}

service.spreadsheets().batchUpdate(spreadsheetId = spreadsheet['spreadsheetId'], body=DATA).execute()

Другие возможности — в описании метода:

В правой можно потренироваться в составлении запроса

Задаём ширину столбца или высоту строки:

service.spreadsheets().batchUpdate(spreadsheetId = spreadsheet['spreadsheetId'], body = {
  "requests": [


    {
      "updateDimensionProperties": {
        "range": {
          "sheetId": 0,
          "dimension": "COLUMNS",  # COLUMNS — столбцы, ROWS — строки
          "startIndex": 0,         # Нумеруются с нуля
          "endIndex": 1            # startIndex берётся включительно, endIndex — не включительно
        },
        "properties": {
          "pixelSize": 200     # Например, 200 пикселей
        },
        "fields": "pixelSize" 
      }
    }
  ]
}).execute()

That’s it!

Ещё по теме:
Эта статья на 50% — упрощенный копипаст вот этого поста на Хабре
Описание метода batchUpdate на сайте Google

Ctrl + ↓ Ранее