Readwise Account Sync:把一个Readwise账户的画线无损同步到另一个,无限续杯

有Readwise用户吗?请问如何在账户间无损迁移信息?继续讨论:

背景

最近了解了Readwise这个平台,挺感兴趣,感觉应该对自己帮助挺大。通过邀请别人可以获得免费使用时长,但是有网友说最多只能凑到一年的时长,到时候还是得付费。而且现在官方说邀请三人以上就需要进行验证,让可使用时长进一步压缩。

需求

楼主想要在不付费的情况下,能够使用多个账号来实现白嫖,比如说账号A邀请时长到达上限之后,创建账号B进行继续使用,但是账号之间的数据如何迁移成为了一个难题。

今天发现使用Readwise自带的csv导出,再导入一个新的账号的时候,很多metadata丢失了,作为一个白嫖怪怎么能够接受呢,于是刚刚挫了一个脚本,利用Reawise的API进行账号之间的数据迁移。

脚本还比较简陋,尚未经过完整的测试,如果发现奇怪的错误,或是同步信息有缺失的,欢迎反馈~

脚本代码:

import requests
import json
import time
from tqdm import tqdm

READWISE_API_URL = 'https://readwise.io/api/v2'


def get_highlights(token):
    """
    Retrieve all highlights from a specified Readwise account using the export API.
    """
    import datetime
    full_data = []
    next_page_cursor = None
    headers = {'Authorization': f'Token {token}'}
    params = {}
    updated_after = None  # Set to retrieve highlights updated after a specific time if needed

    with tqdm(total=100, desc="Progress", unit="times") as pbar:
        while True:
            if next_page_cursor:
                params['pageCursor'] = next_page_cursor
            if updated_after:
                params['updatedAfter'] = updated_after.isoformat()
            print("Making export API request with params " + str(params) + "...")
            response = requests.get(
                url="https://readwise.io/api/v2/export/",
                params=params,
                headers=headers,
            )
            pbar.update(1)

            if response.status_code == 200:
                data = response.json()
                full_data.extend(data.get('results', []))
                next_page_cursor = data.get('nextPageCursor')
                if not next_page_cursor:
                    break
            elif response.status_code == 429:
                retry_after = int(response.headers.get('Retry-After', '60'))
                print(f'Request rate limited, retrying after {retry_after} seconds...')
                for _ in tqdm(range(retry_after), desc="Waiting", unit="seconds"):
                    time.sleep(1)
            else:
                print(f'Error retrieving highlights: {response.status_code}')
                print(response.text)
                break

    return full_data


def upload_highlights(token, full_data):
    """
    Upload highlights to a specified Readwise account using the import API.
    """
    headers = {'Authorization': f'Token {token}', 'Content-Type': 'application/json'}
    url = f'{READWISE_API_URL}/highlights/'

    # Convert highlight data to the required upload format
    highlights_data = []
    for item in full_data:
        for highlight in item.get('highlights', []):
            highlight_data = {
                'text': highlight.get('text', ''),
                'title': item.get('title', ''),
                'author': item.get('author', ''),
                'image_url': item.get('cover_image_url', ''),
                'source_url': item.get('source_url', ''),
                'category': item.get('category', ''),
                'note': highlight.get('note', ''),
                'location': highlight.get('location', ''),
                'location_type': highlight.get('location_type', ''),
                'highlighted_at': highlight.get('highlighted_at', ''),
                'highlight_url': item.get('unique_url', '')
            }
            # Remove keys with empty values
            highlight_data = {k: v for k, v in highlight_data.items() if v != ''}
            highlights_data.append(highlight_data)

    try:
        response = requests.post(url, json={'highlights': highlights_data}, headers=headers)
        if response.status_code == 200:
            print(f'Successfully uploaded {len(highlights_data)} highlights.')
            return response.json()
        elif response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', '60'))
            print(f'Upload rate limited, retrying after {retry_after} seconds...')
            for _ in tqdm(range(retry_after), desc="Waiting", unit="seconds"):
                time.sleep(1)
            return upload_highlights(token, full_data)  # Recursive retry
        else:
            print(f'Upload failed with status code: {response.status_code}')
            print(response.text)
            return None
    except requests.exceptions.RequestException as e:
        print(f'Upload request exception: {e}')
        return None


def main():
    UPLOAD_FROM_FILE = False
    # You can get your token from https://readwise.io/access_token
    source_token = '<YOUR_SOURCE_TOKEN>'
    target_token = '<YOUR_TARGET_TOKEN>'

    if UPLOAD_FROM_FILE:
        # Read highlights from a file, which is generated while retrieving highlights
        with open('highlights.json', 'r') as f:
            highlights = json.load(f)
    else:
        # Retrieve all highlights from the source account
        highlights = get_highlights(source_token)
        with open('highlights.json', 'w') as f:
            json.dump(highlights, f)

    print(f'Total items retrieved: {len(highlights)}.')

    # Upload highlights to the target account
    upload_highlights(target_token, highlights)


if __name__ == '__main__':
    main()

10 Likes

佬动手能力强啊 :smiley:刚提出问题就有解决方案了

1 Like

感谢大佬分享

1 Like

有中文的设置吗

哪个中文设置?


我习惯找语言设置

哦哦,你说readwise啊 :rofl:好像是没有啊

你不是昨晚才发的帖吗?怎么这么快?

跪谢大佬分享啊,及时雨啊。
前阵子刚刚收到这个验证通知。确实是邀请超过3个朋友就要验证了

代码同步很快啊,大佬V5.

牛逼,用上了

如果确实好用。可以跟他说你发展中国家。可以5折购入

请问,是不是只能把highlight同步过来?在reader里面的内容是不是不能够同步?

没试过,可能是不行的

更新了reader的同步,帖子编辑不了了,在Github 仓库更新了。但是由于官方API限制reader里面的划线不能同步 :rofl: 也就是说新账号的Reader里面不会有高亮的画线,但是reader里面的画线似乎会自动更新到highlights里面,因此画线还是能保存的,只不过无法在Reader中显示了。
当作稍后读用的话应该还是没问题的,不过还没详细测试过,可能会有信息丢失。

1 Like

感谢大佬回复,这么久了,还有反馈,感激!已经换了新号,很好用!