不少网页代码里面并没有我们需要的内容,这是因为这些信息是通过 Ajax加载并渲染出来的。这时候就需要我们来分析网页的请求。
需要使用到
requests
BeautifulSoup + 正则表达式
MongoDB (Pymongo)
分析网页
我们需要抓取的链接是 : http://www.toutiao.com/search/?keyword=街拍
里面的 图集
XHR == XMLHttpRequest and Fetch
这个是一个json文件,下面的网址才是实际的 URL(我们需要在代码里用到的)
我们需要从这个页面获取到json文件。
向下滑会有一个新的文件,这个文件通过 offset 递增来改变地址,每20个图集递增一次。
网页是通过 Ajax后台返回json数据然后渲染出来的。
获取 offset=0 的页面
import requests
from requests.exceptions import RequestException
from urllib.parse import urlencode
def get_page_index(offset, keyword):
data = {
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': '20',
'cur_tab': 3
}
url = 'http://www.toutiao.com/search_content/?' + urlencode(data)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('请求索引页出错')
return None
def main():
html = get_page_index(0, '街拍')
print(html)
if __name__ == '__main__':
main()
from requests.exceptions import RequestException
错误判断
结果如下
url
url 是 Requests URL 里面的地址,不是浏览器里面的地址。
data 获取
需要转换为字典的形式。
由于我们需要修改 offset
和 keyword
所以这两个改成变量 存储在 data 里面。
获取每个页面 offset 里面的图集 URL
需要安装 json
pip3 install simplejson
完整代码
import requests
from requests.exceptions import RequestException
from urllib.parse import urlencode
import json
def get_page_index(offset, keyword):
data = {
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': '20',
'cur_tab': 3
}
url = 'http://www.toutiao.com/search_content/?' + urlencode(data)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('请求索引页出错')
return None
def parse_page_index(html):
data = json.loads(html)
if data and 'data' in data.keys():
for item in data.get('data'):
yield item.get('article_url')
def main():
html = get_page_index(0, '街拍')
for url in parse_page_index(html):
print(url)
if __name__ == '__main__':
main()
其中data = json.loads(html)
把 html 变成结构化的 json 格式if data and 'data in data.keys():
判断 data 是在 data的 key 里面。for item in data.get('data'):
做一个循环yield item.get('article_url')
获取 key 为 article_url 的 数据,
yield 是一个参数(构造一个生成器)
结果如下
有些 URL 不对,之后会过滤掉
获取单个页面的图片
查看源代码 ,勾选 Preserve log,选择 All, 可以看到一个和网页链接是一样的文件,这个表示这个是网页原始请求。
也可以直接 勾选 Font 边上的 Doc
完整代码
在获取图片链接的那一步出了问题,因为头条改了。
import json
import os
from urllib.parse import urlencode
import pymongo
import requests
from bs4 import BeautifulSoup
from requests.exceptions import ConnectionError
import re
from multiprocessing import Pool
from hashlib import md5
from json.decoder import JSONDecodeError
from config import * #导入config.py 文件
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]
# 链接mongodb
def get_page_index(offset, keyword):
data = {
'autoload': 'true',
'count': 20,
'cur_tab': 3,
'format': 'json',
'keyword': keyword,
'offset': offset,
}
params = urlencode(data)
base = 'http://www.toutiao.com/search_content/'
url = base + '?' + params
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except ConnectionError:
print('Error occurred')
return None
def download_image(url):
print('Downloading', url)
try:
response = requests.get(url)
if response.status_code == 200:
save_image(response.content)
return None
except ConnectionError:
return None
def save_image(content):
file_path = '{0}/{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
print(file_path)
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
f.close()
def parse_page_index(text):
try:
data = json.loads(text)
if data and 'data' in data.keys():
for item in data.get('data'):
yield item.get('article_url')
except JSONDecodeError:
pass
def get_page_detail(url):
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except ConnectionError:
print('Error occurred')
return None
def parse_page_detail(html, url):
soup = BeautifulSoup(html, 'lxml')
result = soup.select('title')
title = result[0].get_text() if result else ''
images_pattern = re.compile('var gallery = (.*?);', re.S)
result = re.search(images_pattern, html)
if result:
data = json.loads(result.group(1))
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
images = [item.get('url') for item in sub_images]
for image in images: download_image(image)
return {
'title': title,
'url': url,
'images': images
}
def save_to_mongo(result):
if db[MONGO_TABLE].insert(result):
print('Successfully Saved to Mongo', result)
return True
return False
def main(offset):
text = get_page_index(offset, KEYWORD)
urls = parse_page_index(text)
for url in urls:
html = get_page_detail(url)
result = parse_page_detail(html, url)
if result: save_to_mongo(result)
pool = Pool()
groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
pool.map(main, groups)
pool.close()
pool.join()
config.py 完整代码
MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'
GROUP_START = 1
GROUP_END = 20
KEYWORD='街拍'