之前初学 python 时写过一个练手的 Demo, 程序实现了在主机上根据关键词和获得赞同数爬取 "掘金" 中的文章: 利用 Python 爬虫过滤 "掘金" 的关键词检索结果 . 但是这个项目只是简单地实现了功能, 在很多方面都需要加强. 现利用假期在这个程序地基础上修改了一下, 加入了 web 支持, 并且部署到了我私人的服务器上, 大家如果有兴趣可以访问使用: 点我试用 .
首先看一下使用方法和结果:
假设我们在输入框中分别输入了 python(关键字) 和 10(获得赞数下限) 后, 点击按钮:
如果想要获取更多结果, 继续点击 '点击获得更多结果..'就好了, 不过后台设置了不断获取数据的限制 (15 页搜索结果, 一般够用了).
1. 项目结构
其中'main'包中是程序文件,'static'放置静态文件,'templates'放置 html 模板文件,'venv'是虚拟环境,'app.py'是主程序入口文件,'requirements.txt'记录程序所有依赖及版本号.
2. app.py
app.py 负责构建 Flask 应用, 且由于程序功能比较简单, 将视图函数也放置其中,
app = Flask(__name__)
# set the secret key. keep this really secret:
app.secret_key = os.urandom(24)
# 带爬取的 url 地址, 不包含请求参数
ajax_base_url = 'https://search-merger-ms.juejin.im/v1/search'
# 根目录, 返回输入截面
@app.route('/')
def index():
return render_template('input.html')
# 搜索功能视图函数
@app.route('/search')
def search():
try:
baseline = int(request.args.get('baseline')) # 从请求参数中获取文章赞同数的下限值
except ValueError:
raise InvalidParameter('输入框不能为空或者请不要在输入框第二栏中输入非数字字符!')
keyword = quote(request.args.get('keyword')) # 获取搜索的关键字, urllib.parse.quote() 复杂处理 url 中的中文
if keyword is None or len(keyword) == 0:
raise InvalidParameter('输入框不能为空!')
params = {} # 对应的请求参数
params['query'] = keyword
params['page'] = '0'
params['raw_result'] = 'false'
params['src'] = 'web'
new_url = url_manager.build_ajax_url(ajax_base_url, params) # 构建请求地址
craw_json = crawler.craw_one_page(crawler.parse_from_json) # 选择 json 解析器
datas = craw_json(new_url, baseline) # 进行下载, 解析, 获得结果
if datas is None or len(datas) == 0:
return
return render_template('output.html', datas=datas, keyword=request.args.get('keyword'), baseline=baseline) # keyword 传原始值, 否则 next_page 中再进行 quote 则会出错
# 请求获得更多数据
@app.route('/nextPage')
def next_page():
keyword = quote(request.args.get('keyword')) # 获取搜索的关键字, urllib.parse.quote() 复杂处理 url 中的中文
try:
baseline = int(request.args.get('baseline'))
req_page = int(request.args.get('req_page'))
except ValueError:
return redirect(url_for('index'))
if keyword is None or len(keyword) == 0:
return redirect(url_for('index'))
params = {} # 对应的请求参数
params['query'] = keyword
params['page'] = str(req_page)
params['raw_result'] = 'false'
params['src'] = 'web'
new_url = url_manager.build_ajax_url(ajax_base_url, params) # 构建请求地址
craw_json = crawler.craw_one_page(crawler.parse_from_json) # 选择 json 解析器
datas = craw_json(new_url, baseline) # 进行下载, 解析, 获得结果
# 将结果对象构成的列表转完成 json 数组
json_array = []
for data in datas:
json_array.append(data.__dict__)
return jsonify(json_array)
# 参数错误界面
@app.errorhandler(InvalidParameter)
def invalid_param(error):
return render_template('param-error.html', error_message=error.message), error.status_code
if __name__ == '__main__':
app.debug = True
app.run()
app.py 中主要包含了三个视图函数: index(), search(), next_page();search() 负责搜索文章数据, next_page() 负责获取下一页的文章数据.
3. 下载并解析数据
我们可以通过两种不同的 URL 来获取掘金的文章信息, 一种会返回 html 数据, 一种会返回 JSON 数据. 我们选择第二种方式获取 JSON 数据. 下面首先介绍程序的下载器: downloader.py
import urllib.request
def download_json(url):
if url is None:
print('one invalid url is found!')
return None
response = urllib.request.urlopen(url)
if response.getcode() != 200:
print('response from %s is invalid!' % url)
return None
return response.read().decode('utf-8')
通过该方法返回的是 JSON 的字符串数据. 接下来使用解析器来解析 JSON 数据: json_parser.py
# 将 json 字符创解析为一个对象
def json_to_object(json_content):
if json_content is None:
print('parse error!json is None!')
return None
return json.loads(str(json_content))
# 从 JSON 构成的对象中提取出文章的 title,link,collectionCount 等数据, 并将其封装成一个 Bean 对象, 最后将这些对象添加到结果列表中
def build_bean_from_json(json_collection, baseline):
if json_collection is None:
raise ParseError('build bean from json error! json_collection is None!')
list = json_collection['d'] # 文章的列表
result_list = [] # 结果的列表
if list is None or len(list) == 0:
return []
for element in list:
starCount = element['collectionCount'] # 获得的收藏数, 即获得的赞数
if int(starCount) >= baseline: # 如果收藏数不小于 baseline, 则构建结果对象并添加到结果列表中
title = element['title']
link = element['originalUrl']
result = ResultBean(title, link, starCount)
result_list.append(result) # 添加到结果列表中
print(title, link, starCount)
return result_list
4. 爬取器
上面的下载, 解析都可以看作是爬取过程中的工具, 下面我们通过爬取模块将下载和解析过程结合起来: crawler.py
# 爬取一页信息
def craw_one_page(func):
def in_craw_one_page(new_url, baseline=10): # 默认 baseline=10
print('begin to main..')
content = downloader.download_json(new_url) # 根据 URL 获取网页
datas = func(content, baseline) # 一次解析所得的结果
print('main end..')
return datas
return in_craw_one_page
def parse_from_json(content, baseline):
json_collection = json_parser.json_to_object(content)
results = json_parser.build_bean_from_json(json_collection, baseline)
return results
def parse_from_html(content, baseline):
html_parser.build_soup(content) # 使用 BeautifulSoup 将 html 网页构建成 soup 树
results = html_parser.build_bean_from_html(baseline)
return results
这里使用闭包修饰爬取函数, 使我们可以传入 html 或 JSON 对应的解析器.
来源: https://juejin.im/post/5a640416f265da3e585971cb