所以你说 javascript 被用于获取下一组帖子是正确的。这是一个滚动事件触发对服务器的 HTTP 请求的示例。
要查看此内容,您可以使用检查页面 --> 网络工具 --> XHR。清除所有请求并向下滚动。您将看到一个发布请求,并且在右侧您将看到数据。我倾向于搜索我希望看到的内容以确认数据存在。
有两种方法可以处理这种类型的动态内容。
- 模仿 HTTP 请求
- 使用浏览器活动获取数据
第一个总是更好的选择,如果有潜在的 API 点等。那么最好使用它。数据的效率和准确性很重要。
第二种选择依赖于像 selenium 这样速度较慢的包,对 HTML 的更改可能非常脆弱,对代码来说很烦人。
为了收集以下代码的信息,我复制了在网络工具的 XHR 部分中找到的请求的 cURL。我使用一个将其转换为 python 格式的网站,例如 curl.trillworks.com。
HTTP 请求可能只需要对端点的正确 url 进行简单的 get 请求,在您必须输入 headers/params/data 之前先这样做总是好的。在这个例子中,你得到了所有三个,但实际上你只需要一个用户代理和你想要的响应对象的类型,以及用于引导你获得正确响应的数据。
代码示例
import requests
headers = {
'User-Agent': 'M',
'content-type': 'application/json',
}
data = '{"operationName":"Posts","variables":{"year":2019,"month":1,"day":7,"cursor":"MjA=","includeLayout":false},"query":"query Posts($year: Int, $month: Int, $day: Int, $cursor: String) {\\n posts(first: 20, year: $year, month: $month, day: $day, after: $cursor) {\\n edges {\\n node {\\n id\\n ...PostItemList\\n __typename\\n }\\n __typename\\n }\\n pageInfo {\\n endCursor\\n hasNextPage\\n __typename\\n }\\n __typename\\n }\\n}\\n\\nfragment PostItemList on Post {\\n id\\n ...PostItem\\n __typename\\n}\\n\\nfragment PostItem on Post {\\n id\\n _id\\n comments_count\\n name\\n shortened_url\\n slug\\n tagline\\n updated_at\\n ...CollectButton\\n ...PostThumbnail\\n ...PostVoteButton\\n ...TopicFollowButtonList\\n __typename\\n}\\n\\nfragment CollectButton on Post {\\n id\\n name\\n isCollected\\n __typename\\n}\\n\\nfragment PostThumbnail on Post {\\n id\\n name\\n thumbnail {\\n id\\n media_type\\n ...MediaThumbnail\\n __typename\\n }\\n ...PostStatusIcons\\n __typename\\n}\\n\\nfragment MediaThumbnail on Media {\\n id\\n image_uuid\\n __typename\\n}\\n\\nfragment PostStatusIcons on Post {\\n name\\n product_state\\n __typename\\n}\\n\\nfragment PostVoteButton on Post {\\n _id\\n id\\n featured_at\\n updated_at\\n disabled_when_scheduled\\n has_voted\\n ... on Votable {\\n id\\n votes_count\\n __typename\\n }\\n __typename\\n}\\n\\nfragment TopicFollowButtonList on Topicable {\\n id\\n topics {\\n edges {\\n node {\\n id\\n ...TopicFollowButton\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\\n\\nfragment TopicFollowButton on Topic {\\n id\\n slug\\n name\\n isFollowed\\n ...TopicImage\\n __typename\\n}\\n\\nfragment TopicImage on Topic {\\n name\\n image_uuid\\n __typename\\n}\\n"}'
response = requests.post('https://www.producthunt.com/frontend/graphql', headers=headers, data=data)
data = response.json()
for a in response.json()['data']['posts']['edges']:
name = a['node']['name']
subtext = a['node']['tagline']
votes = a['node']['votes_count']
category = a['node']['topics']['edges'][0]['node']['name']
print('-'*20)
print('Name: ',name)
print('Tagline: ',subtext)
print('Category: ',category)
print('Votes: ',votes)
输出
--------------------
Name: Newbook Models
Tagline: Find & book fashion models online
Category: Productivity
Votes: 57
--------------------
Name: 3Leaf Edibles - Quinoa Granola Bite
Tagline: Vegan, low dose, high quality edibles.
Category: Health and Fitness
Votes: 57
--------------------
Name: Payfacile
Tagline: We simplify access to online payment.
Category: Fintech
Votes: 43
--------------------
Name: Halo by Motorola
Tagline: Watch over your baby from above
Category: Kids
Votes: 27
--------------------
Name: Bowflex Max Intelligence
Tagline: Bring the benefits of a personal trainer to your home
Category: Health and Fitness
Votes: 23
解释
标题,你不需要指定user-agent,什么都可以,所以我只使用了一个字符。 content-type 是必要的,但您可以通过试错来发现。
我不会说谎的数据我没有勇气尝试将其分解,我的猜测是交替使用它可能最终无法为您提供所需的数据。该数据的优点之一是您可以更改年/月/日,它会为您提供需要滚动以触发项目的页面的数据。
您没有特别问过这个问题,但是使用 f-strings 之类的东西来为每个 requests.get() 调用输入特定数据将是一种解决方法。
现在你得到的是一个 json 对象,response.json() 方法将这个对象转换为一个 python 字典。所以所有归属于字典的方法都可以使用。
我强烈建议您查看response.json() 字典,除非您这样做,否则您不会真正了解名称、潜台词的含义。但是大多数 json 对象都是嵌套的数据集,因此您通常想要的数据位于许多键的后面,就像在这种情况下一样。对于我循环访问的每篇文章来获取名称/标语。
response.json()['data']['posts']['edges'] 列表项的典型输出
{'node': {'id': '142398',
'__typename': 'Post',
'_id': 'UG9zdC0xNDIzOTg=',
'comments_count': 9,
'name': 'dinely',
'shortened_url': '/r/p/142398',
'slug': 'dinely',
'tagline': 'Up to 50% off top restaurants, no coupon needed',
'updated_at': '2020-04-28T16:55:48-07:00',
'topics': {'edges': [{'node': {'id': '2',
'__typename': 'Topic',
'slug': 'android',
'name': 'Android',
'isFollowed': False,
'image_uuid': 'd3e235c7-437b-4ed1-8298-2ce04eded455'},
'__typename': 'TopicEdge'},
{'node': {'id': '8',
'__typename': 'Topic',
'slug': 'iphone',
'name': 'iPhone',
'isFollowed': False,
'image_uuid': '0ee71650-973d-4933-a3eb-c7201950db4b'},
'__typename': 'TopicEdge'},
{'node': {'id': '159',
'__typename': 'Topic',
'slug': 'drinking',
'name': 'Drinking',
'isFollowed': False,
'image_uuid': '33a0c652-253d-491f-b86b-4e9ba32ee203'},
'__typename': 'TopicEdge'},
{'node': {'id': '250',
'__typename': 'Topic',
'slug': 'travel',
'name': 'Travel',
'isFollowed': False,
'image_uuid': '0a49cae9-ccff-47f1-8998-d0f47c2e7775'},
'__typename': 'TopicEdge'},
{'node': {'id': '278',
'__typename': 'Topic',
'slug': 'e-commerce',
'name': 'E-Commerce',
'isFollowed': False,
'image_uuid': '1aa939fc-89dd-49ae-9fde-d456f9a6c8d2'},
'__typename': 'TopicEdge'}],
'__typename': 'TopicConnection'},
'featured_at': '2019-01-07T03:52:19-08:00',
'disabled_when_scheduled': True,
'has_voted': False,
'votes_count': 58,
'thumbnail': {'id': '703734',
'media_type': 'image',
'__typename': 'Media',
'image_uuid': '96893366-45e6-477e-9bb8-0e04c2070da6'},
'product_state': 'default',
'isCollected': False},
'__typename': 'PostEdge'}