3

我有一个字典列表,我在搜索 JSON url 时填写它。问题是 JSON(由 Google Books API 提供)并不总是完整的。这是对书籍的搜索,据我所见,它们都有 ID、标题和作者,但并非所有书籍都有 imageLinks。下面是一个 JSON 链接作为示例:搜索 Harry Potter

请注意,它总是返回 10 个结果,在此示例中,有 10 个 ID、10 个标题、10 个作者,但只有 4 个 imageLinks。

@app.route('/search', methods=["GET", "POST"])
@login_required
def search():
    if request.method == "POST":
        while True:
            try:
                seek = request.form.get("seek")
                url = f'https://www.googleapis.com/books/v1/volumes?q={seek}'
                response = requests.get(url)
                response.raise_for_status()
                search = response.json()
                seek = search['items']
                infobooks = []
                for i in range(len(seek)):
                    infobooks.append({
                        "book_id": seek[i]['id'],
                        "thumbnail": seek[i]['volumeInfo']['imageLinks']['thumbnail'],
                        "title": seek[i]['volumeInfo']['title'],
                        "authors": seek[i]['volumeInfo']['authors']
                    })
                return render_template("index.html", infobooks=infobooks)
            except (requests.RequestException, KeyError, TypeError, ValueError):
                continue
    else:
        return render_template("index.html")

我使用的方法和我在上面演示的方法,我可以找到 10 个 imageLinks(缩略图),但这需要很长时间!有人对这个请求有什么建议不要花这么长时间吗?或者,当我找不到 imageLink 时,我可以通过某种方式插入“没有封面的书”图像?(不是我想要的,但总比等待结果要好)

4

4 回答 4

5

首先,您的函数将永远不会产生 10 个图像链接,因为 api 将始终返回相同的结果。因此,如果您第一次检索到 4 个 imageLink,第二次将是相同的。除非谷歌更新数据集,但那是你无法控制的。

Google Books Api 最多允许 40 个结果,默认最多 10 个结果。要增加它,您可以添加查询参数maxResults=40,其中 40 可以是等于或小于 40 的任何所需数字。然后您可以在此处决定以编程方式过滤掉所有没有 imageLinks 的结果,或者留下它们并添加一个没有结果的图像 URL。此外,并非每个结果都返回作者列表,这在此示例中也已修复。第三方 api 总是检查空/空结果,不要冒险,因为它可能会破坏您的代码。我使用 .get 来避免在处理 json 时发生任何异常。

虽然我没有在这个例子中添加它,但你也可以使用谷歌图书提供的分页来分页以获得更多结果。

例子:

@app.route('/search', methods=["GET", "POST"])
@login_required
def search():
    if request.method == "POST":
        seek = request.form.get("seek")
        url = f'https://www.googleapis.com/books/v1/volumes?q={seek}&maxResults=40'
        response = requests.get(url)
        response.raise_for_status()
        results = response.json().get('items', [])
        infobooks = []
        no_image = {'smallThumbnail': 'http://no-image-link/image-small.jpeg', 'thumbnail': 'http://no-image-link/image.jpeg'}
        for result in results:
            info = result.get('volumeInfo', {})
            imageLinks = info.get("imageLinks")
            infobooks.append({
                "book_id": result.get('id'),
                "thumbnail": imageLinks if imageLinks else no_image,
                "title": info.get('title'),
                "authors": info.get('authors')
            })
        return render_template("index.html", infobooks=infobooks)
    else:
        return render_template("index.html")

谷歌图书 API 文档: https ://developers.google.com/books/docs/v1/using

于 2021-06-08T22:10:55.537 回答
1

从您的问题来看,问题所在并不是很明显(因此缺乏参与)。在玩了一会儿代码和 API 之后,我现在对这个问题有了更好的理解。

问题在于 Google 图书 API 并不总是包含每个项目的图像缩略图。

您当前针对此问题的解决方案是重试整个搜索,直到所有字段都有图像缩略图。但是想想这是否真的需要。也许你可以把它分开。在我的测试中,我发现没有图像缩略图的书籍经常切换。这意味着如果您只是继续重试,直到查询的所有结果都有缩略图,这将需要很长时间。

该解决方案应尝试单独查询每本书的缩略图。在 X 次尝试后,它应该默认为“可用图像”,以避免向 API 发送垃圾邮件。

正如您在帖子中已经发现的那样,您可以从原始搜索查询中获取每本书的卷 ID。然后,您可以使用API 调用单独查询每个卷。

我创建了一些代码来验证它是否有效。而且只有一本书的末尾没有图像缩略图。这段代码仍有很大的改进空间,但我将把它留给你作为练习。

import requests

# Max attempts to get an image
_MAX_ATTEMPTS = 5

# No Image Picture
no_img_link = 'https://upload.wikimedia.org/wikipedia/en/6/60/No_Picture.jpg'


def search_book(seek):
    url = f'https://www.googleapis.com/books/v1/volumes?q={seek}'
    response = requests.get(url)
    search = response.json()
    volumes = search['items']

    # Get ID's of all the volumes
    volume_ids = [volume['id'] for volume in volumes]

    # Storage for the results
    book_info_collection = []

    # Loop over all the volume ids
    for volume_id in volume_ids:

        # Attempt to get the thumbnail a couple times
        for i in range(_MAX_ATTEMPTS):
            url = f'https://www.googleapis.com/books/v1/volumes/{volume_id}'
            response = requests.get(url)
            volume = response.json()
            try:
                thumbnail = volume['volumeInfo']['imageLinks']['thumbnail']
            except KeyError:
                print(f'Failed for {volume_id}')
                if i < _MAX_ATTEMPTS - 1:
                    # We still have attempts left, keep going
                    continue
                # Failed on the last attempt, use a default image
                thumbnail = no_img_link
                print('Using Default')

            # Create dict with book info
            book_info = {
                "book_id": volume_id,
                "thumbnail": thumbnail,
                "title": volume['volumeInfo']['title'],
                "authors": volume['volumeInfo']['authors']
            }

            # Add to collection
            book_info_collection.append(book_info)
            break

    return book_info_collection


books = search_book('Harry Potter')
print(books)

于 2021-06-08T00:20:59.753 回答
1

您已添加您希望它快速加载。这意味着您不能在 python 中进行重试,因为您在 python 中进行的任何重试都意味着更长的页面加载时间。

这意味着您必须在浏览器中进行加载。您可以使用与纯 python 方法相同的方法。首先,您只需使用请求中的所有图像,并对所有没有图像的卷发出额外的请求。这意味着您有 2 个端点,一个用于 volume_information。另一个端点只获取一个卷的数据。

请注意,我使用术语卷而不是书,因为这也是 Google API 使用的。

现在,JavaScript 不是我的强项,所以我在这里提供的解决方案还有很大的改进空间。

我用烧瓶做了这个例子。此示例应该可以帮助您实施适合您的特定应用的解决方案。

额外说明:在我的测试中,我注意到,某些区域比其他区域更频繁地响应所有缩略图。API 根据您的 IP 地址发送不同的响应。如果我将我的 IP 设置在美国,我经常会得到所有缩略图而无需重试。我正在使用 VPN 来执行此操作,但可能还有其他解决方案。

应用程序.py

import time

from flask import Flask, render_template, request, jsonify
import requests

app = Flask(__name__)


@app.route('/')
def landing():
    return render_template('index.html', volumes=get_volumes('Harry Potter'))


@app.route('/get_volume_info')
def get_volume_info_endpoint():
    volume_id = request.args.get('volume_id')
    if volume_id is None:
        # Return an error if no volume id was provided
        return jsonify({'error': 'must provide argument'}), 400

    # To stop spamming the API
    time.sleep(0.250)
    
    # Request volume data
    url = f'https://www.googleapis.com/books/v1/volumes/{volume_id}'
    response = requests.get(url)
    volume = response.json()

    # Get the info using the helper function
    volume_info = get_volume_info(volume, volume_id)
    
    # Return json object with the info
    return jsonify(volume_info), 200


def get_volumes(search):
    # Make request
    url = f'https://www.googleapis.com/books/v1/volumes?q={search}'
    response = requests.get(url)
    data = response.json()

    # Get the volumes
    volumes = data['items']

    # Add list to store results
    volume_info_collection = []

    # Loop over the volumes
    for volume in volumes:
        volume_id = volume['id']
        
        # Get volume info using helper function
        volume_info = get_volume_info(volume, volume_id)

        # Add it to the result
        volume_info_collection.append(volume_info)
    
    return volume_info_collection


def get_volume_info(volume, volume_id):
    # Get basic information
    volume_title = volume['volumeInfo']['title']
    volume_authors = volume['volumeInfo']['authors']
    
    # Set default value for thumbnail
    volume_thumbnail = None
    try:
        volume_thumbnail = volume['volumeInfo']['imageLinks']['thumbnail']
    except KeyError:
        # Failed we keep the None value
        print('Failed to get thumbnail')
    
    # Fill in the dict
    volume_info = {
        'volume_id': volume_id,
        'volume_title': volume_title,
        'volume_authors': volume_authors,
        'volume_thumbnail': volume_thumbnail
    }
    
    # Return volume info
    return volume_info


if __name__ == '__main__':
    app.run()

模板 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        let tracker = {}

        function get_thumbnail(id) {
            let url = '/get_volume_info?volume_id=' + id
            fetch(url).then(function (response) {
                return response.json();
            }).then(function (data) {
                console.log(data);
                return data['volume_thumbnail']
            }).catch(function () {
                console.log("Error");
            });
        }

        function image_load_failed(id) {
            let element = document.getElementById(id)

            if (isNaN(tracker[id])) {
                tracker[id] = 0
            }
            console.log(tracker[id])

            if (tracker[id] >= 5) {
                element.src = 'https://via.placeholder.com/128x196C/O%20https://placeholder.com/'
                return
            }

            element.src = get_thumbnail(id)
            tracker[id]++
        }
    </script>
</head>
<body>

<table>
    <tr>
        <th>ID</th>
        <th>Title</th>
        <th>Authors</th>
        <th>Thumbnail</th>
    </tr>
    {% for volume in volumes %}
        <tr>
            <td>{{ volume['volume_id'] }}</td>
            <td>{{ volume['volume_title'] }}</td>
            <td>{{ volume['volume_authors'] }}</td>
            <td><img id="{{ volume['volume_id'] }}" src="{{ volume['volume_thumbnail'] }}"
                     onerror="image_load_failed('{{ volume['volume_id'] }}')"></td>
        </tr>
    {% endfor %}

</table>

</body>
</html>
于 2021-06-08T17:32:19.340 回答
1

添加虚拟图像 URL

"book_id": seek[i]['id'] or 'dummy_url'
于 2021-06-12T03:45:53.110 回答