Metadata-Version: 2.1
Name: spider-renderer
Version: 0.1.3
Summary: Building a modular crawler template system based on Jinja2.
Home-page: https://github.com/ffujiawei/spider-renderer
Author: White Turing
Author-email: fu.jiawei@outlook.com
License: Apache 2.0
Description: # 基于 Jinja2 构建模块化爬虫模板系统
        
        懒惰是人类的天性，重复性工作必然令人不胜其烦，哪怕是简单的复制粘贴，还经常担心会不会弄错了。在这个还不到一个月的实习中，我所做的工作就是解析一系列相同主题的网页，写出对应的爬虫程序。而这些程序，首先由于公司内部规范，很多字段都是相同的，只需填入相应网站的信息即可；其次很多网站的结构具有相似性，解析方法可以在一定程度上通用。
        
        一开始，我只是复制粘贴，但很快我就开始厌烦了，比如引号经常看不准填外面了、一直手动输入网页信息等等，这些真的无聊又可能输错或者忘记改掉。于是，我开始寻求模板渲染的解决方案。
        
        在学习 *Scrapy* 过程中，我了解到官方就有根据模板生成爬虫的命令：
        
        ```shell
        $ scrapy genspider -l
        Available templates:
          basic
          crawl
          csvfeed
          xmlfeed
         
        $ scrapy genspider example example.com
        Created spider 'example' using template 'basic'
        ```
        
        顺藤摸瓜我找到了实现该功能的源码：
        
        ```python
        def render_templatefile(path, **kwargs):
            with open(path, 'rb') as fp:
                raw = fp.read().decode('utf8')
            content = string.Template(raw).substitute(**kwargs)
            render_path = path[:-len('.tmpl')] if path.endswith('.tmpl') else path
            with open(render_path, 'wb') as fp:
                fp.write(content.encode('utf8'))
            if path.endswith('.tmpl'):
                os.remove(path)
        ```
        
        于是我就根据该函数设计了自己的爬虫生成函数：
        
        ```python
        def genspider(area, city, home_url, page_url, all_page, regex, tmpl, dst):
            response = requests.get(home_url)
            string = response.content.decode('utf-8')
            source = re.findall(r'(?is)<title>(.*?)</title>', string)[0]
            tvars = {
                'source': source,
                'area': area,
                'city': city,
                'region': source[:3],
                'home_url': home_url,
                'author': 'Rustle Karl',
                'page_url': page_url,
                'all_page': all_page + 1,
                'regex': regex,
            }
            spider_file = '%s/%s.py' % (dst, name)
            shutil.copyfile(tmpl, spider_file)
            render_templatefile(spider_file, **tvars)
        ```
        
        然而，过了几天我就又觉得不够方便了。因为我写了不止一个模板，这些不同的模板里却有部分相同的代码，若是这部分相同的代码有所改进，每次都必须复制粘贴多个模板，有时候未即时同步，过一会就忘了哪个是最新部分。
        
        我又开始寻找新的方法，Jinja2 频繁出现在我的搜索结果页里。网上基本都是用 Jinja2 渲染网页的内容，很容易让人觉得它只能渲染 HTML 文件，我没有先入为主地这样认为。于是，我开始学习 Jinja2 的相关知识，尝试边学变实现我所想的功能。具体过程不再赘述，这里就放上我现在已经用了挺久的一些解决方法：
        
        1. **模板渲染函数**
        
        ```python
        def render_templatefile(dst, tmpl, **kwargs):
            env = Environment(loader=FileSystemLoader(
                kwargs.pop('tmplpath', './templates')))
            template = env.get_template(tmpl)
            with open(dst, 'w', encoding='utf-8') as fp:
                fp.write(template.render(**kwargs))
        ```
        
        - **dst**：程序生成的位置
        - **tmpl**：渲染时使用的模板文件
        - **kwargs**：接受多个键值对，用于填充模板，其中 `tmplpath` 参数是模板文件的目录，默认为 `./templates`
        
        2. **程序文件生成函数**
        
        ```python
        def replace_all(string, words=tuple()):
            for w in words:
                string = string.replace(w, '')
            return string
        
        def genspider(area, city, name, home_url, page_url, all_page, regex, tmpl, dst):
            response = retry_get(home_url)
            if not response:
                raise Exception('Request failed, invalid URL or parameter.')
        
            try:
                string = response.content.decode('utf8')
            except UnicodeDecodeError:
                string = response.content.decode('gbk')
        
            _source = re.findall(r'(?is)<title>(.*?)</title>', string)[0]
            words = ('&nbsp;', '-', '_', '()')
            source = re.sub(r'\s', '', replace_all(_source, words))
        
            tvars = {
                'date': datetime.now().strftime('%Y-%m-%d %I:%M:%S %p'),
                'name': name,
                'source': source,
                'area': area,
                'city': city,
                'region': replace_all(source, list(city))[:3] if name[5] == '0' else '',
                'home_url': home_url,
                'author': 'Rustle Karl',
                'page_url': page_url,
                'all_page': all_page + 1,
                'regex': regex,
            }
        
            spider = '%s/%s.py' % (dst, name)
            render_templatefile(spider, tmpl, **tvars)
        ```
        
        - **regex**：是一个正则表达式，用于提取网页的相关信息
        - 各种参数都可以根据实际情况增减，这里只是一种参考
        
        3. **填入数据的技巧**
        
        我觉得引号真的挺难选准填入的，所以，对于无法双击全部选中的情况，我用了下面的方法：
        
        ```python
        home_url = '''
        https://www.google.com
        '''.strip()
        ```
        
        ## 爬虫模板的编写
        
        模块化爬虫模板系统，我的设想是将程序的模板拆分到最小粒度，比如拆分为固定的头部信息、请求翻页函数、详情页解析等功能部分，然后编写不同的相似网页的解析程序模板，比如表格解析、纯文本解析等的方法，遇到类似网页时只需选取已有的模板进行自由组合即可，而今后遇到新的页面格式，只需编写新的解析部分，放入模板库，解释说明可用的情况。若是多人合作还可以共享模板库，大家一起讨论贡献最好的解析方法。
        
        然而，**这其实还是只能算是我的一个想法，目前我所实现的模板也只是可用于自己的工作**，更何况我不太敢向其他人说出自己的想法，或许别人有更好的解决方案而我未尝得知。
        
        我目前的做法是将一个爬虫程序尽可能拆分为独立的模块：
        
        - 头部固定模块 - 包括导入库、自定义的函数和爬虫类的静态属性信息；
        - 首页解析模块 - 用于解析详情页的链接；
        - 表格头部模块 - 不同的表格解析方法仍有相同的预处理，因此有必要独立出来；
        - 表格解析模块 - 不同的表格解析方法都分别作为一个模块；
        - 文字解析模块 - 纯文本内容的解析，中间可嵌入表格解析等。
        
        > 相关代码和模块模板示例，等我将相关数据脱敏后会放到 https://github.com/ffujiawei/spider-renderer 上开源。
        
        毫无疑问，编写模板模块是最核心的部分。其中我最希望实现的动态自由组合的功能，仅依靠 Jinja2 似乎无法实现，因为 Jinja2 只能在编写模板时就指定包含或继承，这就必须另外寻求解决方案（也可能我对 Jinja2 的了解还不够深入）。
        
        但由于效益问题（毕竟现在的方法已经基本满足我一开始的诉求了）架构、实现细节等我还没深入构思，我不知道是否有朝一日能够实现这样一个系统，也许吧。
        
Keywords: spider,renderer,scrapy
Platform: UNKNOWN
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.6.0
Description-Content-Type: text/markdown
