描述
上一篇(一个六年经验的python后端是怎么学习用java写API的(1)设定场景,选择框架dropwizard)确定需求后,第一步需要实现实现 Extracter 模块来抓取微信文章,代码为 pirate 。
pirate 是由我的 django 脚手架 original 实现的,文件上传提供了七牛和腾讯云两个 backend,部署提供了默认的配置文件,因此只要关注具体的微信的抓取逻辑即可。
核心表讲解
pirate/original/extracter/models.py
微信文章表,正常设计,大字段拆表。
13 class WXArticle(TimeStampedModel):
14 raw_url = models.CharField(max_length=1023)
15 title = models.CharField(max_length=255)
16 cover = models.CharField(max_length=255)
17 description = models.CharField(max_length=255, default='')
18 is_active = models.BooleanField(default=False)
19 is_delete = models.BooleanField(default=False)
98 class WXArticleContent(TimeStampedModel):
99 article_id = models.IntegerField(unique=True)
100 content = models.TextField()
101 body_script = models.TextField(default='')
pirate/original/extracter/models.py
图片上传缓存表,因为有时候会重复抓取一篇文章(例如编辑后需要重新写文章),而每次抓取文章时会将里面的图片上传到我们的 cdn,添加上传文件缓存表来减少重复的图片上传操作。
188 class URLFileUploadCache(TimeStampedModel):
189 raw_url = models.CharField(blank=True, default='', max_length=512)
190 url = models.CharField(blank=True, default='', max_length=255)
191 raw_url_md5 = models.CharField(blank=True, default='', max_length=64, db_index=True)
抓取
观察微信文章发现,文章 div id=js_content 为文章正文,其中标题和 cover 图在 meta 里面,使用 requests 抓取文章原文,BeautifulSoup 过滤需要的 dom,正则找出需要替换的图片,upload_handler 选择七牛去上传图片并且替换掉原图地址。
21 @classmethod
22 def spider_url(cls, raw_url):
23 assert raw_url.startswith('https://mp.weixin.qq.com/') or raw_url.startswith('http://mp.weixin.qq.com/')
24 content = tools.spider_request(raw_url)
25 description = content_text = title = cover = ''
26 body_script = ''
27 if content:
28 b = BeautifulSoup(content, 'html.parser')
29 content_dom = b.find('div', id='js_content')
30 title_dom = b.find('meta', property="og:title")
31 cover_dome = b.find('meta', property="og:image")
32 if title_dom:
33 title = title_dom.attrs['content']
34 if cover_dome:
35 cover = cover_dome.attrs['content']
36 raw_url = cover
37 upload_cache = URLFileUploadCache.get_cached_objects(raw_url, get_last=True)
38 if upload_cache:
39 cover = upload_cache[0].url
40 else:
41 upload_data = tools.upload_file_from_url(raw_url)
42 url = upload_data['url']
43 if url:
44 URLFileUploadCache.new_cache(raw_url, url)
45 cover = url
46 if content_dom:
47 content_text = unicode(content_dom)
48 ss = b.body.find_all('script')
49 body_script = ''.join(unicode(s) for s in ss)
50 urls = []
51 for _re in constants.WEIXIN_IMAGE_RES:
52 urls.extend(_re.findall(content_text))
53 descriptions = constants.WEIXIN_DESCRIPTION_RE.findall(content)
54 if descriptions:
55 description = BeautifulSoup(descriptions[0]).meta.attrs.get('content', '')
56 mapper = {}
57 for raw_url in urls:
58 if raw_url in mapper:
59 continue
60 upload_cache = URLFileUploadCache.get_cached_objects(raw_url, get_last=True)
61 if upload_cache:
62 mapper[raw_url] = upload_cache[0].url
63 else:
64 upload_data = tools.upload_file_from_url(raw_url, )
65 url = upload_data['url']
66 if url:
67 URLFileUploadCache.new_cache(raw_url, url)
68 mapper[raw_url] = url
69 for raw_url, url in mapper.iteritems():
70 if not url:
71 continue
72 content_text = content_text.replace(raw_url, url)
73 return {
74 'raw_url': raw_url,
75 'title': title,
76 'cover': cover,
77 'content': content_text,
78 'description': description,
79 'body_script': body_script,
80 }
上传文件到七牛的 upload_handler
13 POLICY = settings.FILE_CALLBACK_POLICY or {
14 'callbackUrl': settings.FILEUPLOAD_CALLBACK_URL,
15 'callbackBody': 'bucket=$(bucket)&key=$(key)&filename=$(fname)&filesize=$(fsize)',
16 'insertOnly': 1,
17 }
18
19
20 class UploadHandler(object):
21
22 def __init__(self, key, secret, bucket):
23 self.key = key
24 self.secret = secret
25 self.bucket = bucket
26 self.backend = qiniu
27 self._backend_auth = Auth(self.key, self.secret)
28
29 def _upload_token(self, key, expires=3600, policy=None):
30 token = self._backend_auth.upload_token(self.bucket, key=key, expires=expires, policy=policy)
31 return token
32
33 def upload_file(self, key, data, policy=None, fname='file_name'):
34 if policy is None:
35 policy = POLICY
36 uptoken = self._upload_token(key, policy=policy)
37 return self.backend.put_data(uptoken, key, data, fname=fname)
38
39 def get_download_url(self, key):
40 return '{}{}'.format(settings.FILE_DOWNLOAD_PREFIX, key)
CDN 配置
- 注册或登录七牛账号 https://www.qiniu.com/
- 通过控制台进入对象存储
- 创建一个公开的存储空间,名字即 FILE_UPLOAD_BUCKET
- 为了让域名好看一些,配置 CDN 加速域名,根据步骤操作即可(有一些 dns 的操作)。FILE_DOWNLOAD_PREFIX
- 个人中心秘钥管理获取 upload_handler 需要的 FILE_UPLOAD_KEY、FILE_UPLOAD_SECRET
执行部署
- 创建对应 mysql 数据库
- 创建 virtualenv,source 后 pip install 项目路径下的 requirements/base.txt
- 创建 config/settings/private_production.py,见下面代码段
- 数据库 migrate,cd 到 original 目录,执行./manage.py migrate --settings=config.settings.production
- 启动项目 ./manage.py runserver 0.0.0.0:8976 (端口号随意)
- 当然建议用 supervisor + nginx,见deploy目录下的相关例子
import os
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dbname', # 改成你的数据库名字
'HOST': os.environ.get('ORIGINAL_MYSQL_HOST', 'localhost'), # 数据库host
'USER': os.environ.get('ORIGINAL_MYSQL_USER', 'db_username'), # 改成你的数据库user
'PASSWORD': os.environ.get('ORIGINAL_MYSQL_PASSWORD', 'db_password'), # 改成你的数据库password
'PORT': os.environ.get('ORIGINAL_MYSQL_PORT', 3306),
'OPTIONS': {'charset': 'utf8mb4'},
}
}
FILE_UPLOAD_BACKEND = 'qiniu'
FILE_UPLOAD_KEY = 'i6fdSECQjLfF' # 改成你的qiniu key,现在这里是假的
FILE_UPLOAD_SECRET = 'adfiuerqp' # 改成你的qiniu secret
FILE_UPLOAD_BUCKET = 'reworkdev' # 改成你的qiniu bucket
FILE_CALLBACK_POLICY = {}
FILE_DOWNLOAD_PREFIX = '' # 改成你的host, 例如 http://cdn.myhost.com/
FILEUPLOAD_CALLBACK_URL = # 改成你自己host的对应地址, 例如 https://www.myhost.com/api/v1/file/upload/callback/
postman 测试接口
查看数据库
接下来
基本物料接口准备就绪,下面就可以正式开始 Java 框架的学习。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。