爬虫有风险,使用须谨慎。

科技爱好者周刊内容非常有趣,但也繁杂,检索起来很不方便。为了方便信息查找,使用爬虫获取文本信息,放到数据库中方便搜索。

目的:

  1. 抓取所有周刊数据,主要想要内容部分,读者评论部分可以不用
  2. 内容可分: 标题,子标题,文本,图片,链接等(图片没下载)
  3. 对全部内容做索引,可以方便查询与搜索 (数据量太小12000多条,完全不是大数据)

设计:

  1. 首先把网页整体下载下来,每期约100K(仅文本),约300期,共需要30M
  2. 下载使用单线程
  3. 对下载的网页进一步提取有用信息,并存储为文本(或写入数据库)
  4. 使用分词,对标题和内容做索引 (没用到)
  5. 使用命令行,或sql进行查询

主要Challenge:

  1. 数据结构化存储
  2. 分词工具没用过

对表的设计很简单,就是一个表存取数据:

DROP TABLE IF EXISTS t_weekly;
CREATE TABLE t_weekly (
  id INT NOT NULL AUTO_INCREMENT,
  title VARCHAR(255),
  category VARCHAR(128),
  content TEXT,
  url VARCHAR(255),
  ctime TIMESTAMP,
  utime TIMESTAMP,
  last_updated_by VARCHAR(32),
  PRIMARY KEY (id)
);

下载之后的网页,结构化比较好。使用Beautifulsoup 来处理xpath,获取新闻、文摘、工具、本周话题等信息。但这个地方也是花时间最多的地方,有很多需要处理细节的地方。

部分代码:

def downloads_to(self, base_save_to_folder):
  save_to_folder = self.get_save_to_folder()

  if save_to_folder == self.ERROR_INVALID_URL:
    return self.ERROR_INVALID_URL

  save_to = base_save_to_folder + save_to_folder

  if not os.path.exists(save_to):
    os.makedirs(save_to)

  response = requests.get(self.url)
  if response.status_code == 200:
    with open(save_to + save_to_folder + '.html', "wb") as f:
      f.write(response.content)
    print("Page saved")

    html_content = response.content.decode('utf-8')
    pattern = r'<link rel="next" href="(https://[^"]+)"'
    match = re.search(pattern, html_content)

    if match:
      next_link = match.group(1)
      return next_link
    else:
      print(self.ERROR_NO_NEXT_LINK)
      return self.ERROR_NO_NEXT_LINK
  else:
    print(self.ERROR_RESPONSE_CODE, response.status_code)
    return self.ERROR_RESPONSE_CODE
 
def extract_content(self, file_path):
  contents_map = {}

  with open(file_path, 'r', encoding='utf-8') as reader: 
    html_content = reader.read()
    #print(html)
    soup = BeautifulSoup(html_content, 'html.parser') 
    
    h1 = soup.find('h1')
    category = f'{h1.text}'
    contents_map[category] = []

    print('Category:', category)

    main_content_div = soup.find('div', id='main-content')
    paragraphs = main_content_div.find_all(['p', 'h2'])
    for tag in paragraphs:
      if tag.name == 'h2':
        break
      
      if not(tag.text.startswith('这里记录过去一周,我看到的值得分享的东西')
          or tag.text.startswith('这里记录每周值得分享的科技内容,周五发布') 
          or tag.text.startswith('欢迎投稿,或推荐你自己的项目,请前往') 
          or tag.text.startswith('本杂志开源')
          or tag.text.startswith('周刊讨论区的帖子《谁在招人')):
      contents_map[category].append(tag.text)


    titles = soup.find_all('h2')
    for title in titles:
      if title.text == '历史上的本周' \
        or title.text == '欢迎订阅' \
        or title.text == '回顾' :
        break

    category = f'{title.text}'
    print(category) 
    contents_map[category] = []

    next_element = title.find_next_sibling()
    while next_element and next_element.name != 'h2':
      if next_element.name == 'p' or next_element.name == 'ul' or next_element.name == 'blockquote':
      content = next_element.text
      content = re.sub(r'\n{2,}', '\n', content) 

      if bool(re.match(r'\d+、', content)):
        content = '~~' + content 
      
      if bool(re.match(r'~~\d+、', content)) and len(content) > 4:  
        content = content + '。'

      contents_map[category].append(content) 
      #print(content)

      next_element = next_element.find_next_sibling()

  return contents_map

可以使用SQL查询了 image