firecrawlrb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +109 -0
- data/lib/firecrawlrb/client.rb +102 -0
- data/lib/firecrawlrb.rb +81 -0
- metadata +43 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6750678d7f156f49281a7ee81cebebab83b09a9241c7ab04efe28041d6498498
|
|
4
|
+
data.tar.gz: 4cbf4932ccb8f642d33627ff8fb4933f37c90888c36ed1ad0de57e129bd76731
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 806574da79377099e9d58f93e873145681bad840355397918099562179fc57d48add2a41f597251d3bb9ddf574d24aa1aa34af487f14f1ff4af9a6f40b61ad55
|
|
7
|
+
data.tar.gz: 8a4fd4b8f0996211fce4ca0686e7290d75c297dae1ee7b12a1810396e17a3ce8504cbb33c51e3565cd4c15c839092200f79e0e85cc6a060b1beff75e80227aba
|
data/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Firecrawlrb
|
|
2
|
+
|
|
3
|
+
A zero-dependency Ruby client for the [Firecrawl API](https://www.firecrawl.dev). Uses only Ruby standard library (`Net::HTTP`, `URI`, `JSON`).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'firecrawlrb', path: '../firecrawlrb'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Configuration
|
|
22
|
+
|
|
23
|
+
You can provide your Firecrawl API key via an environment variable:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export FIRECRAWL_API_KEY='fc-your_api_key_here'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or via a configuration block in a Rails initializer (`config/initializers/firecrawlrb.rb`):
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require 'firecrawlrb'
|
|
33
|
+
|
|
34
|
+
Firecrawlrb.configure do |config|
|
|
35
|
+
config.api_key = ENV['FIRECRAWL_API_KEY']
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or pass it directly when initializing the client:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
client = Firecrawlrb::Client.new(api_key: 'fc-your_api_key_here')
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### LLM JSON Extraction
|
|
46
|
+
|
|
47
|
+
You can specify a target URL, schema (concise Ruby Hash/Array or raw JSON Schema), and prompt to receive back JSON matching your schema:
|
|
48
|
+
|
|
49
|
+
#### Concise Ruby Schema Example
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
client = Firecrawlrb::Client.new
|
|
53
|
+
|
|
54
|
+
schema = {
|
|
55
|
+
products: [
|
|
56
|
+
{
|
|
57
|
+
url: :string,
|
|
58
|
+
image_url: :string,
|
|
59
|
+
price: :string,
|
|
60
|
+
name: :string
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
prompt = "extract the products from the main product list site for https://www.blindster.com"
|
|
66
|
+
|
|
67
|
+
products = client.extract(
|
|
68
|
+
url: "https://www.blindster.com",
|
|
69
|
+
schema: schema,
|
|
70
|
+
prompt: prompt
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
puts products
|
|
74
|
+
# => { "products" => [ { "name" => "...", "price" => "...", "image_url" => "...", "url" => "..." } ] }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Raw JSON Schema Example
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
schema = {
|
|
81
|
+
type: "object",
|
|
82
|
+
required: [],
|
|
83
|
+
properties: {
|
|
84
|
+
products: {
|
|
85
|
+
type: "array",
|
|
86
|
+
items: {
|
|
87
|
+
type: "object",
|
|
88
|
+
required: [],
|
|
89
|
+
properties: {
|
|
90
|
+
url: { type: "string" },
|
|
91
|
+
image_url: { type: "string" },
|
|
92
|
+
price: { type: "string" },
|
|
93
|
+
name: { type: "string" }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
products = client.extract(
|
|
101
|
+
url: "https://www.blindster.com",
|
|
102
|
+
schema: schema,
|
|
103
|
+
prompt: prompt
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Firecrawlrb
|
|
8
|
+
class Client
|
|
9
|
+
API_URL = "https://api.firecrawl.dev/v2/scrape".freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :api_key, :api_url
|
|
12
|
+
|
|
13
|
+
def initialize(api_key: nil, api_url: nil)
|
|
14
|
+
@api_key = api_key || ENV['FIRECRAWL_API_KEY'] || Firecrawlrb.configuration.api_key
|
|
15
|
+
@api_url = api_url || Firecrawlrb.configuration.api_url || API_URL
|
|
16
|
+
raise ArgumentError, "API key is required. Set ENV['FIRECRAWL_API_KEY'], use Firecrawlrb.configure, or pass it to Client.new" if @api_key.nil? || @api_key.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Extract JSON matching a schema using Firecrawl v2 API
|
|
20
|
+
#
|
|
21
|
+
# @param url_or_kw [String, nil] Target URL if positional argument
|
|
22
|
+
# @param schema_arg [Hash, Array, nil] Schema if positional argument
|
|
23
|
+
# @param prompt_arg [String, nil] Prompt if positional argument
|
|
24
|
+
# @param url [String, nil] Target URL if keyword argument
|
|
25
|
+
# @param schema [Hash, Array, nil] Schema if keyword argument
|
|
26
|
+
# @param prompt [String, nil] Prompt if keyword argument
|
|
27
|
+
# @param only_main_content [Boolean] Whether to extract main content only (default true)
|
|
28
|
+
# @param max_age [Integer] Cache max age in ms (default 172800000)
|
|
29
|
+
# @param options [Hash] Additional options to merge into payload
|
|
30
|
+
# @return [Hash, Array, nil] The parsed extracted JSON
|
|
31
|
+
def extract(url_or_kw = nil, schema_arg = nil, prompt_arg = nil, url: nil, schema: nil, prompt: nil, only_main_content: true, max_age: 172800000, options: {})
|
|
32
|
+
target_url = url || url_or_kw
|
|
33
|
+
target_schema = schema || schema_arg
|
|
34
|
+
target_prompt = prompt || prompt_arg
|
|
35
|
+
|
|
36
|
+
raise ArgumentError, "url is required" if target_url.nil? || target_url.to_s.empty?
|
|
37
|
+
raise ArgumentError, "schema is required" if target_schema.nil?
|
|
38
|
+
|
|
39
|
+
normalized_schema = Firecrawlrb::Schema.normalize(target_schema)
|
|
40
|
+
|
|
41
|
+
format_hash = {
|
|
42
|
+
type: "json",
|
|
43
|
+
schema: normalized_schema
|
|
44
|
+
}
|
|
45
|
+
format_hash[:prompt] = target_prompt if target_prompt && !target_prompt.to_s.empty?
|
|
46
|
+
|
|
47
|
+
payload = {
|
|
48
|
+
url: target_url.to_s,
|
|
49
|
+
onlyMainContent: only_main_content,
|
|
50
|
+
maxAge: max_age,
|
|
51
|
+
parsers: [],
|
|
52
|
+
formats: [format_hash]
|
|
53
|
+
}.merge(options)
|
|
54
|
+
|
|
55
|
+
response = request(payload)
|
|
56
|
+
parsed = parse_response(response)
|
|
57
|
+
|
|
58
|
+
if parsed.is_a?(Hash) && parsed.key?("data") && parsed["data"].is_a?(Hash) && parsed["data"].key?("json")
|
|
59
|
+
parsed["data"]["json"]
|
|
60
|
+
elsif parsed.is_a?(Hash) && parsed.key?("json")
|
|
61
|
+
parsed["json"]
|
|
62
|
+
else
|
|
63
|
+
parsed
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
alias scrape extract
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def request(payload)
|
|
72
|
+
uri = URI.parse(@api_url)
|
|
73
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
74
|
+
http.use_ssl = (uri.scheme == "https")
|
|
75
|
+
http.read_timeout = 180
|
|
76
|
+
http.open_timeout = 180
|
|
77
|
+
|
|
78
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
|
79
|
+
req['Authorization'] = "Bearer #{@api_key}"
|
|
80
|
+
req['Content-Type'] = 'application/json'
|
|
81
|
+
req.body = JSON.generate(payload)
|
|
82
|
+
|
|
83
|
+
http.request(req)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_response(response)
|
|
87
|
+
body = response.body
|
|
88
|
+
begin
|
|
89
|
+
parsed = JSON.parse(body)
|
|
90
|
+
rescue JSON::ParserError
|
|
91
|
+
raise Error, "Invalid JSON response from Firecrawl API (#{response.code}): #{body.to_s[0..100]}..."
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
95
|
+
error_msg = parsed['error'] || parsed['message'] || response.message
|
|
96
|
+
raise Error, "Firecrawl API Error (#{response.code}): #{error_msg}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
parsed
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/firecrawlrb.rb
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "firecrawlrb/client"
|
|
4
|
+
|
|
5
|
+
module Firecrawlrb
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
class Configuration
|
|
9
|
+
attr_accessor :api_key, :api_url
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@api_key = nil
|
|
13
|
+
@api_url = "https://api.firecrawl.dev/v2/scrape"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Schema
|
|
18
|
+
TYPE_MAPPING = {
|
|
19
|
+
string: "string",
|
|
20
|
+
number: "number",
|
|
21
|
+
float: "number",
|
|
22
|
+
decimal: "number",
|
|
23
|
+
integer: "integer",
|
|
24
|
+
int: "integer",
|
|
25
|
+
boolean: "boolean",
|
|
26
|
+
bool: "boolean",
|
|
27
|
+
array: "array",
|
|
28
|
+
object: "object"
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def self.normalize(input)
|
|
32
|
+
case input
|
|
33
|
+
when Hash
|
|
34
|
+
# If it already looks like a raw JSON Schema (has :type or "type" key)
|
|
35
|
+
if input.key?(:type) || input.key?("type") || input.key?(:properties) || input.key?("properties")
|
|
36
|
+
input
|
|
37
|
+
else
|
|
38
|
+
properties = {}
|
|
39
|
+
input.each do |key, value|
|
|
40
|
+
properties[key.to_s] = normalize(value)
|
|
41
|
+
end
|
|
42
|
+
{
|
|
43
|
+
type: "object",
|
|
44
|
+
required: [],
|
|
45
|
+
properties: properties
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
when Array
|
|
49
|
+
if input.empty?
|
|
50
|
+
{ type: "array", items: { type: "string" } }
|
|
51
|
+
else
|
|
52
|
+
{ type: "array", items: normalize(input.first) }
|
|
53
|
+
end
|
|
54
|
+
when Symbol, String
|
|
55
|
+
type_str = input.to_s.downcase.to_sym
|
|
56
|
+
json_type = TYPE_MAPPING[type_str] || input.to_s
|
|
57
|
+
{ type: json_type }
|
|
58
|
+
else
|
|
59
|
+
{ type: "string" }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
def configuration
|
|
66
|
+
@configuration ||= Configuration.new
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def configuration=(config)
|
|
70
|
+
@configuration = config
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def configure
|
|
74
|
+
yield(configuration)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def reset
|
|
78
|
+
@configuration = Configuration.new
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: firecrawlrb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Firecrawlrb Author
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Simple zero-dependency Ruby gem for Firecrawl API LLM JSON extraction.
|
|
13
|
+
email:
|
|
14
|
+
- author@example.com
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- README.md
|
|
20
|
+
- lib/firecrawlrb.rb
|
|
21
|
+
- lib/firecrawlrb/client.rb
|
|
22
|
+
homepage: ''
|
|
23
|
+
licenses:
|
|
24
|
+
- MIT
|
|
25
|
+
metadata: {}
|
|
26
|
+
rdoc_options: []
|
|
27
|
+
require_paths:
|
|
28
|
+
- lib
|
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 2.5.0
|
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
requirements: []
|
|
40
|
+
rubygems_version: 3.6.9
|
|
41
|
+
specification_version: 4
|
|
42
|
+
summary: A zero-dependency Ruby client for the Firecrawl API
|
|
43
|
+
test_files: []
|