Scrapy爬取伯樂線上文章
首先搭建虛擬環境,建立工程
scrapy startproject ArticleSpider
cd ArticleSpider
scrapy genspider jobbole blog.jobbole.com
修改start_urls = ['http://blog.jobbole.com/all-posts/']
獲取網頁資訊
ArticleSpider/spiders/jobbole.py
# -*- coding: utf-8 -*- import datetime import re from urllib import parse import scrapy from scrapy import Request from ArticleSpider.items import JobBoleArticleItem from ArticleSpider.utils.common import get_md5 class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] def parse(self, response): """ 1. 從文章列表中獲取文章連結交給scrapy下載 再進行解析 2. 獲取下一頁的連結並交給scrapy下載, 下載完成後在使用parse函式進行解析 :param response: :return: """ post_nodes = response.css("#archive .floated-thumb .post-thumb a") for post_node in post_nodes: image_url = post_node.css("img::attr(src)").extract_first("") post_url = post_node.css("::attr(href)").extract_first("") yield Request(url=parse.urljoin(response.url, post_url), meta={"front_image_url": image_url}, callback=self.parse_detail) # 提取下一頁 next_url = response.css(".next.page-numbers::attr(href)").extract_first() if next_url: yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) def parse_detail(self, response): """ 獲取具體欄位 :param response: :return: """ article_item = JobBoleArticleItem() # 通過css選擇器提取欄位 front_image_url = response.meta.get("front_image_url", "") #文章封面圖 title = response.css(".entry-header h1::text").extract()[0] create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·","").strip() praise_nums = response.css(".vote-post-up h10::text").extract()[0] fav_nums = response.css(".bookmark-btn::text").extract()[0] match_re = re.match(".*?(\d+).*", fav_nums) if match_re: fav_nums = int(match_re.group(1)) else: fav_nums = 0 comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0] match_re = re.match(".*?(\d+).*", comment_nums) if match_re: comment_nums = int(match_re.group(1)) else: comment_nums = 0 content = response.css("div.entry").extract()[0] tag_list = response.css("p.entry-meta-hide-on-mobile a::text").extract() tag_list = [element for element in tag_list if not element.strip().endswith("評論")] tags = ",".join(tag_list) article_item["url_object_id"] = get_md5(response.url) article_item["title"] = title article_item["url"] = response.url try: create_date = datetime.datetime.strptime(create_date, "%Y/%m/%d").date() except Exception as e: create_date = datetime.datetime.now().date() article_item["create_date"] = create_date article_item["front_image_url"] = [front_image_url] article_item["praise_nums"] = praise_nums article_item["comment_nums"] = comment_nums article_item["fav_nums"] = fav_nums article_item["tags"] = tags article_item["content"] = content yield article_item
ArticleSpider/items.py
class JobBoleArticleItem(scrapy.Item): title = scrapy.Field() create_date = scrapy.Field() url = scrapy.Field() url_object_id = scrapy.Field() front_image_url = scrapy.Field() front_image_path = scrapy.Field() praise_nums = scrapy.Field() comment_nums = scrapy.Field() fav_nums = scrapy.Field() tags = scrapy.Field() content = scrapy.Field()
ArticleSpider/pipelines.py
from scrapy.pipelines.images import ImagesPipeline class ArticlespiderPipeline(object): def process_item(self, item, spider): return item class ArticleImagePipeline(ImagesPipeline): def item_completed(self, results, item, info): if "front_image_url" in item: for ok, value in results: image_file_path = value["path"] item["front_image_path"] = image_file_path return item
ArticleSpider/settings.py
建立一個資料夾ArticleSpider/images
,用來儲存圖片
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
# 'scrapy.pipelines.images.ImagesPipeline': 1,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
}
IMAGES_URLS_FIELD = "front_image_url"
project_dir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir, 'images')
資料入庫
/*
Navicat MySQL Data Transfer
Source Server : 2233
Source Server Version : 50723
Source Host : localhost:3306
Source Database : article_spider
Target Server Type : MYSQL
Target Server Version : 50723
File Encoding : 65001
Date: 2018-10-15 11:19:07
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for jobbole_article
-- ----------------------------
DROP TABLE IF EXISTS `jobbole_article`;
CREATE TABLE `jobbole_article` (
`title` varchar(255) NOT NULL,
`create_date` date DEFAULT NULL,
`url` varchar(255) NOT NULL,
`url_object_id` varchar(50) NOT NULL,
`front_image_url` varchar(255) DEFAULT NULL,
`front_image_path` varchar(255) DEFAULT NULL,
`praise_nums` int(11) NOT NULL DEFAULT '0',
`comment_nums` int(11) NOT NULL DEFAULT '0',
`fav_nums` int(11) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL,
PRIMARY KEY (`url_object_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
編寫pipline
import MySQLdb
import MySQLdb.cursors
from scrapy.pipelines.images import ImagesPipeline
from twisted.enterprise import adbapi
from ArticleSpider import settings
from ArticleSpider.settings import MYSQL_DBNAME, MYSQL_PASSWORD, MYSQL_USER, MYSQL_HOST
class ArticlespiderPipeline(object):
def process_item(self, item, spider):
return item
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
if "front_image_url" in item:
for ok, value in results:
image_file_path = value["path"]
item["front_image_path"] = image_file_path
return item
class MysqlPipeline(object):
# 採用同步的機制寫入mysql
def __init__(self):
self.conn = MySQLdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DBNAME, charset="utf8",
use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
insert_sql = """
insert into jobbole_article(title, url,url_object_id, create_date, fav_nums)
VALUES (%s, %s, %s, %s, %s)
"""
self.cursor.execute(insert_sql,
(item["title"], item["url"], item["url_object_id"], item["create_date"], item["fav_nums"]))
self.conn.commit()
class MysqlTwistedPipline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
dbparms = dict(
host=settings["MYSQL_HOST"],
db=settings["MYSQL_DBNAME"],
user=settings["MYSQL_USER"],
passwd=settings["MYSQL_PASSWORD"],
charset='utf8',
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
# 使用twisted將mysql插入變成非同步執行
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error, item, spider) # 處理異常
def handle_error(self, failure, item, spider):
# 處理非同步插入的異常
print(failure)
def do_insert(self, cursor, item):
# 執行具體的插入
# 根據不同的item 構建不同的sql語句並插入到mysql中
insert_sql = """
insert into jobbole_article(title, url,url_object_id, create_date, front_image_url, front_image_path, praise_nums, comment_nums,fav_nums, tags,content )
VALUES (%s, %s, %s, %s,%s,%s, %s, %s, %s,%s,%s)
"""
cursor.execute(insert_sql,
(item["title"], item["url"], item["url_object_id"], item["create_date"], item["front_image_url"],
item["front_image_path"], item["praise_nums"], item["comment_nums"], item["fav_nums"], item["tags"],
item["content"]))
settings.py
ITEM_PIPELINES = {
# 'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
# 'scrapy.pipelines.images.ImagesPipeline': 1,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
'ArticleSpider.pipelines.MysqlTwistedPipline': 2
}
item loader
ArticleSpider/spiders/jobbole.py
# 通過item loader載入item
front_image_url = response.meta.get("front_image_url", "") # 文章封面圖
item_loader = ArticleItemLoader(item=JobBoleArticleItem(), response=response)
item_loader.add_css("title", ".entry-header h1::text")
item_loader.add_value("url", response.url)
item_loader.add_value("url_object_id", get_md5(response.url))
item_loader.add_css("create_date", "p.entry-meta-hide-on-mobile::text")
item_loader.add_value("front_image_url", [front_image_url])
item_loader.add_css("praise_nums", ".vote-post-up h10::text")
item_loader.add_css("comment_nums", "a[href='#article-comment'] span::text")
item_loader.add_css("fav_nums", ".bookmark-btn::text")
item_loader.add_css("tags", "p.entry-meta-hide-on-mobile a::text")
item_loader.add_css("content", "div.entry")
article_item = item_loader.load_item()
yield article_item
ArticleSpider/items.py
```python
class ArticleItemLoader(ItemLoader):
# 自定義itemloader
default_output_processor = TakeFirst()
def date_convert(value):
try:
create_date = datetime.datetime.strptime(value, "%Y/%m/%d").date()
except Exception as e:
create_date = datetime.datetime.now().date()
return create_date
def get_nums(value):
match_re = re.match(".?(\d+).", value)
if match_re:
nums = int(match_re.group(1))
else:
nums = 0
return nums
def remove_comment_tags(value):
# 去掉tag中提取的評論
if "評論" in value:
return ""
else:
return value
def return_value(value):
return value
class JobBoleArticleItem(scrapy.Item):
title = scrapy.Field()
create_date = scrapy.Field(
input_processor=MapCompose(date_convert),
)
url = scrapy.Field()
url_object_id = scrapy.Field()
front_image_url = scrapy.Field(
output_processor=MapCompose(return_value)
)
front_image_path = scrapy.Field()
praise_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
comment_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
fav_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
tags = scrapy.Field(
input_processor=MapCompose(remove_comment_tags),
output_processor=Join(",")
)
content = scrapy.Field()
```