mirror of
https://github.com/hashiromer/Upwork-Jobs-scraper-.git
synced 2025-12-17 09:46:01 +00:00
Updated Upwork API
This commit is contained in:
commit
c7c2998595
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
data/
|
||||
|
||||
|
||||
all_jobs.json
|
||||
|
||||
*.json
|
||||
*.csv
|
||||
*.7z
|
||||
oryxBuildBinary
|
||||
*.ipynb
|
||||
*.txt
|
||||
*.env
|
||||
!.env.example
|
||||
132
JobSearchQuery.gql
Normal file
132
JobSearchQuery.gql
Normal file
@ -0,0 +1,132 @@
|
||||
query UserJobSearch($requestVariables: UserJobSearchV1Request!) {
|
||||
search {
|
||||
universalSearchNuxt {
|
||||
userJobSearchV1(request: $requestVariables) {
|
||||
paging {
|
||||
total
|
||||
offset
|
||||
count
|
||||
}
|
||||
facets {
|
||||
jobType {
|
||||
key
|
||||
value
|
||||
}
|
||||
workload {
|
||||
key
|
||||
value
|
||||
}
|
||||
clientHires {
|
||||
key
|
||||
value
|
||||
}
|
||||
durationV3 {
|
||||
key
|
||||
value
|
||||
}
|
||||
amount {
|
||||
key
|
||||
value
|
||||
}
|
||||
contractorTier {
|
||||
key
|
||||
value
|
||||
}
|
||||
contractToHire {
|
||||
key
|
||||
value
|
||||
}
|
||||
connects: connectPrice {
|
||||
key
|
||||
value
|
||||
}
|
||||
paymentVerified: payment {
|
||||
key
|
||||
value
|
||||
}
|
||||
proposals {
|
||||
key
|
||||
value
|
||||
}
|
||||
previousClients {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
results {
|
||||
id
|
||||
title
|
||||
description
|
||||
relevanceEncoded
|
||||
ontologySkills {
|
||||
uid
|
||||
parentSkillUid
|
||||
prefLabel
|
||||
prettyName: prefLabel
|
||||
freeText
|
||||
highlighted
|
||||
}
|
||||
isSTSVectorSearchResult
|
||||
connectPrice
|
||||
applied
|
||||
upworkHistoryData {
|
||||
client {
|
||||
paymentVerificationStatus
|
||||
country
|
||||
totalReviews
|
||||
totalFeedback
|
||||
hasFinancialPrivacy
|
||||
totalSpent {
|
||||
isoCurrencyCode
|
||||
amount
|
||||
}
|
||||
}
|
||||
freelancerClientRelation {
|
||||
lastContractRid
|
||||
companyName
|
||||
lastContractTitle
|
||||
}
|
||||
}
|
||||
jobTile {
|
||||
job {
|
||||
id
|
||||
ciphertext: cipherText
|
||||
jobType
|
||||
weeklyRetainerBudget
|
||||
hourlyBudgetMax
|
||||
hourlyBudgetMin
|
||||
hourlyEngagementType
|
||||
contractorTier
|
||||
sourcingTimestamp
|
||||
createTime
|
||||
publishTime
|
||||
enterpriseJob
|
||||
personsToHire
|
||||
premium
|
||||
totalApplicants
|
||||
hourlyEngagementDuration {
|
||||
rid
|
||||
label
|
||||
weeks
|
||||
mtime
|
||||
ctime
|
||||
}
|
||||
fixedPriceAmount {
|
||||
isoCurrencyCode
|
||||
amount
|
||||
}
|
||||
fixedPriceEngagementDuration {
|
||||
id
|
||||
rid
|
||||
label
|
||||
weeks
|
||||
ctime
|
||||
mtime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Upwork-Jobs-scraper
|
||||
|
||||
The code uses Upwork's internal Api to scrape new jobs posted on Upwork. I am not using headless browser due to two reasons.
|
||||
|
||||
1. Using headless bowsers is way more resource intensive compared to using an Api.
|
||||
|
||||
2. I don't have to deal with HTML parsing, the api returns json which can be directly passed to downstream systems.
|
||||
|
||||
# Note
|
||||
|
||||
The code uses Golang instead of Python because Upwork filters bots by checking TLS signatures of incoming requests. Unfortunately, I could not
|
||||
find a way to do it in pure Python because Python is compiled with openssl and popular browsers do not use it. Chrome uses BoringSSl and firefox uses NSS.
|
||||
These SSL libraries use different extensions and cipher suites which makes detection of TLS level configurations a more robust method to detect bot traffic.
|
||||
|
||||
Golang is a more lower level language compared to Python, so it allows changing network level configurations. I am using `cycletls` package in golang which makes spoofing TLS/JA3 fingerprints an easy task.
|
||||
|
||||
# How can you contribute?
|
||||
|
||||
These are some of the features I think could be useful.
|
||||
|
||||
- Better error handling with channels
|
||||
- Add support for automatic proxy rotation. It can be extremely effective when used in conjunction with go routines.
|
||||
- Add Api schema for Upwork Api.
|
||||
- Add more scrapers, a lot of logic is platform agnostic which could be used to build scrapers for more platforms.
|
||||
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
||||
module scrapers
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/Danny-Dasilva/CycleTLS/cycletls v0.0.0-20220620102923-c84d740b4757
|
||||
|
||||
require (
|
||||
github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6 // indirect
|
||||
github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/net v0.0.0-20220615171555-694bf12d69de // indirect
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
53
go.sum
Normal file
53
go.sum
Normal file
@ -0,0 +1,53 @@
|
||||
github.com/Danny-Dasilva/CycleTLS/cycletls v0.0.0-20220620102923-c84d740b4757 h1:QH22vGS2DO07khPzKe3/CgFevznQkip5WNGEsQX7mFI=
|
||||
github.com/Danny-Dasilva/CycleTLS/cycletls v0.0.0-20220620102923-c84d740b4757/go.mod h1:R4Hj85bdRH8zqymQ/oZUCmEsODgP3NpUvTEJtaVai7Y=
|
||||
github.com/Danny-Dasilva/fhttp v0.0.0-20220418170016-5ea1c560e6a8/go.mod h1:t534vrahRNn9ax1tRiYSUvwJSa9jWaYYgETlfodBPm4=
|
||||
github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6 h1:Wzbitazy0HugGNRACX7ZB1En21LT/TiVF6YbxoTTqN8=
|
||||
github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6/go.mod h1:2IT2IFG+d+zzFuj3+ksGtVytcCBsF402zMNWHsWhD2U=
|
||||
github.com/Danny-Dasilva/utls v0.0.0-20220418055514-7c61e0dbb504/go.mod h1:A2g8gPTJWDD3Y4iCTNon2vG3VcjdTBcgWBlZtopfNxU=
|
||||
github.com/Danny-Dasilva/utls v0.0.0-20220418175931-f38e470e04f2/go.mod h1:A2g8gPTJWDD3Y4iCTNon2vG3VcjdTBcgWBlZtopfNxU=
|
||||
github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e h1:tqiguW0yAcIwQBQtD+d2rjBnboqB7CwG1OZ12F8avX8=
|
||||
github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e/go.mod h1:ssfbVNUfWJVRfW41RTpedOUlGXSq3J6aLmirUVkDgJk=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220615171555-694bf12d69de h1:ogOG2+P6LjO2j55AkRScrkB2BFpd+Z8TY2wcM0Z3MGo=
|
||||
golang.org/x/net v0.0.0-20220615171555-694bf12d69de/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
13
main.go
Normal file
13
main.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "scrapers/upwork"
|
||||
|
||||
func main() {
|
||||
|
||||
p := upwork.InitPipeline()
|
||||
err := p.Run("")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
35
network/httpClient.go
Normal file
35
network/httpClient.go
Normal file
@ -0,0 +1,35 @@
|
||||
package network
|
||||
|
||||
import "github.com/Danny-Dasilva/CycleTLS/cycletls"
|
||||
|
||||
type Client struct {
|
||||
httpClient cycletls.CycleTLS
|
||||
options cycletls.Options
|
||||
}
|
||||
|
||||
func InitClient(headers map[string]string) *Client {
|
||||
client := cycletls.Init()
|
||||
options := cycletls.Options{
|
||||
Body: "",
|
||||
Ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
|
||||
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
|
||||
Headers: headers,
|
||||
}
|
||||
|
||||
httpClient := Client{
|
||||
httpClient: client,
|
||||
options: options,
|
||||
}
|
||||
|
||||
return &httpClient
|
||||
}
|
||||
|
||||
func (c Client) GetRequest(url string) (cycletls.Response, error) {
|
||||
return c.httpClient.Do(url, c.options, "GET")
|
||||
}
|
||||
|
||||
func (c Client) PostRequest(url string, body string) (cycletls.Response, error) {
|
||||
c.options.Body = body
|
||||
c.options.Headers["Content-Type"] = "application/json"
|
||||
return c.httpClient.Do(url, c.options, "POST")
|
||||
}
|
||||
2
upwork/.env.example
Normal file
2
upwork/.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
authorization
|
||||
cookie
|
||||
828
upwork/types.go
Normal file
828
upwork/types.go
Normal file
@ -0,0 +1,828 @@
|
||||
package upwork
|
||||
|
||||
import "time"
|
||||
|
||||
type LoggedOutError struct {
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
}
|
||||
|
||||
type UpworkApiResponse struct {
|
||||
URL string `json:"url"`
|
||||
SearchGUID string `json:"searchGuid"`
|
||||
SearchResults struct {
|
||||
Q string `json:"q"`
|
||||
Paging struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Count int `json:"count"`
|
||||
ResultSetTs int `json:"resultSetTs"`
|
||||
} `json:"paging"`
|
||||
Spellcheck struct {
|
||||
CorrectedQueries []interface{} `json:"corrected_queries"`
|
||||
} `json:"spellcheck"`
|
||||
Jobs []struct {
|
||||
Title string `json:"title"`
|
||||
CreatedOn time.Time `json:"createdOn"`
|
||||
Type int `json:"type"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
Description string `json:"description"`
|
||||
Category2 interface{} `json:"category2"`
|
||||
Subcategory2 interface{} `json:"subcategory2"`
|
||||
Skills []interface{} `json:"skills"`
|
||||
Duration string `json:"duration"`
|
||||
ShortDuration string `json:"shortDuration"`
|
||||
DurationLabel string `json:"durationLabel"`
|
||||
Engagement string `json:"engagement"`
|
||||
ShortEngagement string `json:"shortEngagement"`
|
||||
Amount struct {
|
||||
CurrencyCode string `json:"currencyCode"`
|
||||
Amount int `json:"amount"`
|
||||
} `json:"amount"`
|
||||
Recno int `json:"recno"`
|
||||
UID string `json:"uid"`
|
||||
Client struct {
|
||||
PaymentVerificationStatus int `json:"paymentVerificationStatus"`
|
||||
Location struct {
|
||||
Country string `json:"country"`
|
||||
} `json:"location"`
|
||||
TotalSpent float64 `json:"totalSpent"`
|
||||
TotalReviews int `json:"totalReviews"`
|
||||
TotalFeedback float64 `json:"totalFeedback"`
|
||||
CompanyRid int `json:"companyRid"`
|
||||
CompanyName interface{} `json:"companyName"`
|
||||
EdcUserID int `json:"edcUserId"`
|
||||
LastContractPlatform interface{} `json:"lastContractPlatform"`
|
||||
LastContractRid int `json:"lastContractRid"`
|
||||
LastContractTitle interface{} `json:"lastContractTitle"`
|
||||
FeedbackText string `json:"feedbackText"`
|
||||
CompanyOrgUID string `json:"companyOrgUid"`
|
||||
HasFinancialPrivacy bool `json:"hasFinancialPrivacy"`
|
||||
} `json:"client"`
|
||||
FreelancersToHire int `json:"freelancersToHire"`
|
||||
RelevanceEncoded string `json:"relevanceEncoded"`
|
||||
EnterpriseJob bool `json:"enterpriseJob"`
|
||||
TierText string `json:"tierText"`
|
||||
Tier string `json:"tier"`
|
||||
TierLabel string `json:"tierLabel"`
|
||||
IsSaved interface{} `json:"isSaved"`
|
||||
Feedback string `json:"feedback"`
|
||||
ProposalsTier string `json:"proposalsTier"`
|
||||
IsApplied bool `json:"isApplied"`
|
||||
Sticky bool `json:"sticky"`
|
||||
StickyLabel string `json:"stickyLabel"`
|
||||
JobTs string `json:"jobTs"`
|
||||
PrefFreelancerLocationMandatory bool `json:"prefFreelancerLocationMandatory"`
|
||||
PrefFreelancerLocation []interface{} `json:"prefFreelancerLocation"`
|
||||
Premium bool `json:"premium"`
|
||||
PlusBadge interface{} `json:"plusBadge"`
|
||||
PublishedOn time.Time `json:"publishedOn"`
|
||||
RenewedOn time.Time `json:"renewedOn"`
|
||||
SandsService interface{} `json:"sandsService"`
|
||||
SandsSpec interface{} `json:"sandsSpec"`
|
||||
SandsAttrs interface{} `json:"sandsAttrs"`
|
||||
Occupation interface{} `json:"occupation"`
|
||||
Attrs []struct {
|
||||
ParentSkillUID interface{} `json:"parentSkillUid"`
|
||||
FreeText interface{} `json:"freeText"`
|
||||
SkillType int `json:"skillType"`
|
||||
UID string `json:"uid"`
|
||||
Highlighted bool `json:"highlighted"`
|
||||
PrettyName string `json:"prettyName"`
|
||||
} `json:"attrs"`
|
||||
IsLocal bool `json:"isLocal"`
|
||||
WorkType interface{} `json:"workType"`
|
||||
Locations []interface{} `json:"locations"`
|
||||
Occupations struct {
|
||||
Category struct {
|
||||
UID string `json:"uid"`
|
||||
PrefLabel string `json:"prefLabel"`
|
||||
} `json:"category"`
|
||||
Subcategories []struct {
|
||||
UID string `json:"uid"`
|
||||
PrefLabel string `json:"prefLabel"`
|
||||
} `json:"subcategories"`
|
||||
Oservice struct {
|
||||
UID string `json:"uid"`
|
||||
PrefLabel string `json:"prefLabel"`
|
||||
} `json:"oservice"`
|
||||
} `json:"occupations"`
|
||||
WeeklyBudget interface{} `json:"weeklyBudget"`
|
||||
HourlyBudgetText interface{} `json:"hourlyBudgetText"`
|
||||
Tags []interface{} `json:"tags"`
|
||||
ClientRelation interface{} `json:"clientRelation"`
|
||||
TotalFreelancersToHire int `json:"totalFreelancersToHire"`
|
||||
TeamUID interface{} `json:"teamUid"`
|
||||
MultipleFreelancersToHirePredicted interface{} `json:"multipleFreelancersToHirePredicted"`
|
||||
ConnectPrice int `json:"connectPrice"`
|
||||
} `json:"jobs"`
|
||||
Facets struct {
|
||||
JobType struct {
|
||||
Num0 int `json:"0"`
|
||||
Num1 int `json:"1"`
|
||||
WeeklyRetainer int `json:"weekly_retainer"`
|
||||
} `json:"jobType"`
|
||||
Workload struct {
|
||||
None int `json:"none"`
|
||||
FullTime int `json:"full_time"`
|
||||
AsNeeded int `json:"as_needed"`
|
||||
} `json:"workload"`
|
||||
Duration struct {
|
||||
Ongoing int `json:"ongoing"`
|
||||
Week int `json:"week"`
|
||||
Month int `json:"month"`
|
||||
Semester int `json:"semester"`
|
||||
Quarter int `json:"quarter"`
|
||||
} `json:"duration"`
|
||||
ClientHires struct {
|
||||
Num0 int `json:"0"`
|
||||
One9 int `json:"1-9"`
|
||||
One0 int `json:"10-"`
|
||||
} `json:"clientHires"`
|
||||
Budget struct {
|
||||
Num0 int `json:"0"`
|
||||
Num50 int `json:"50"`
|
||||
Num100 int `json:"100"`
|
||||
Num250 int `json:"250"`
|
||||
Num500 int `json:"500"`
|
||||
Num1000 int `json:"1000"`
|
||||
Num2000 int `json:"2000"`
|
||||
Num5000 int `json:"5000"`
|
||||
Num10000 int `json:"10000"`
|
||||
Num20000 int `json:"20000"`
|
||||
Num100000 int `json:"100000"`
|
||||
} `json:"budget"`
|
||||
ContractorTier struct {
|
||||
Num1 int `json:"1"`
|
||||
Num2 int `json:"2"`
|
||||
Num3 int `json:"3"`
|
||||
} `json:"contractorTier"`
|
||||
Categories2 []struct {
|
||||
Category struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
ResultCount int `json:"resultCount"`
|
||||
} `json:"category"`
|
||||
Subcategories []struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
ResultCount int `json:"resultCount"`
|
||||
} `json:"subcategories"`
|
||||
} `json:"categories2"`
|
||||
PreviousClients struct {
|
||||
All int `json:"all"`
|
||||
} `json:"previousClients"`
|
||||
Proposals struct {
|
||||
Five9 int `json:"5-9"`
|
||||
Two049 int `json:"20-49"`
|
||||
Zero4 int `json:"0-4"`
|
||||
One014 int `json:"10-14"`
|
||||
NAMING_FAILED int `json:"*"`
|
||||
One519 int `json:"15-19"`
|
||||
} `json:"proposals"`
|
||||
Amount struct {
|
||||
FiveThousandPlus int `json:"5000-"`
|
||||
ANY int `json:"*"`
|
||||
Hundred_to_Five_hundred int `json:"100-499"`
|
||||
Thousand_to_five_thousand int `json:"1000-4999"`
|
||||
ZeroTo_Ninty_Nine int `json:"0-99"`
|
||||
FiveHundred_to_thousand int `json:"500-999"`
|
||||
} `json:"amount"`
|
||||
UserLocationMatch struct {
|
||||
Num1 int `json:"1"`
|
||||
} `json:"userLocationMatch"`
|
||||
Services interface{} `json:"services"`
|
||||
Specs interface{} `json:"specs"`
|
||||
Attrs interface{} `json:"attrs"`
|
||||
DurationV2 struct {
|
||||
Week int `json:"week"`
|
||||
Ongoing int `json:"ongoing"`
|
||||
Month int `json:"month"`
|
||||
} `json:"durationV2"`
|
||||
Enterprise struct {
|
||||
True int `json:"true"`
|
||||
False int `json:"false"`
|
||||
} `json:"enterprise"`
|
||||
UserDomesticJobsLastDays struct {
|
||||
Num7 int `json:"7"`
|
||||
} `json:"userDomesticJobsLastDays"`
|
||||
DurationV3 struct {
|
||||
Weeks int `json:"weeks"`
|
||||
Months int `json:"months"`
|
||||
Semester int `json:"semester"`
|
||||
Ongoing int `json:"ongoing"`
|
||||
} `json:"durationV3"`
|
||||
Occupations []struct {
|
||||
TaxonomyLevel string `json:"taxonomyLevel"`
|
||||
UID string `json:"uid"`
|
||||
Label string `json:"label"`
|
||||
Count int `json:"count"`
|
||||
Occupations []struct {
|
||||
TaxonomyLevel string `json:"taxonomyLevel"`
|
||||
UID string `json:"uid"`
|
||||
Label string `json:"label"`
|
||||
Count int `json:"count"`
|
||||
Occupations []struct {
|
||||
TaxonomyLevel string `json:"taxonomyLevel"`
|
||||
UID string `json:"uid"`
|
||||
Label string `json:"label"`
|
||||
Count int `json:"count"`
|
||||
Occupations interface{} `json:"occupations"`
|
||||
} `json:"occupations"`
|
||||
} `json:"occupations"`
|
||||
} `json:"occupations"`
|
||||
EngagementDuration interface{} `json:"engagementDuration"`
|
||||
FreelancersNeeded struct {
|
||||
Zero1 int `json:"0-1"`
|
||||
Six int `json:"6-"`
|
||||
Two5 int `json:"2-5"`
|
||||
Two int `json:"2-"`
|
||||
Zero int `json:"0-"`
|
||||
} `json:"freelancersNeeded"`
|
||||
Location struct {
|
||||
SouthAmerica int `json:"South America"`
|
||||
PapuaNewGuinea int `json:"Papua New Guinea"`
|
||||
Cambodia int `json:"Cambodia"`
|
||||
Paraguay int `json:"Paraguay"`
|
||||
Kazakhstan int `json:"Kazakhstan"`
|
||||
Bahamas int `json:"Bahamas"`
|
||||
MarshallIslands int `json:"Marshall Islands"`
|
||||
Mali int `json:"Mali"`
|
||||
Panama int `json:"Panama"`
|
||||
Guadeloupe int `json:"Guadeloupe"`
|
||||
EasternAsia int `json:"Eastern Asia"`
|
||||
Laos int `json:"Laos"`
|
||||
Argentina int `json:"Argentina"`
|
||||
Seychelles int `json:"Seychelles"`
|
||||
Africa int `json:"Africa"`
|
||||
Zambia int `json:"Zambia"`
|
||||
Belize int `json:"Belize"`
|
||||
Bahrain int `json:"Bahrain"`
|
||||
Congo int `json:"Congo"`
|
||||
GuineaBissau int `json:"Guinea-Bissau"`
|
||||
SaintBarthelemy int `json:"Saint Barthelemy"`
|
||||
Namibia int `json:"Namibia"`
|
||||
PalestinianTerritories int `json:"Palestinian Territories"`
|
||||
Finland int `json:"Finland"`
|
||||
FaroeIslands int `json:"Faroe Islands"`
|
||||
NetherlandsAntilles int `json:"Netherlands Antilles"`
|
||||
Georgia int `json:"Georgia"`
|
||||
SaintKittsAndNevis int `json:"Saint Kitts and Nevis"`
|
||||
Yemen int `json:"Yemen"`
|
||||
PuertoRico int `json:"Puerto Rico"`
|
||||
Madagascar int `json:"Madagascar"`
|
||||
Aruba int `json:"Aruba"`
|
||||
Sweden int `json:"Sweden"`
|
||||
Malawi int `json:"Malawi"`
|
||||
Andorra int `json:"Andorra"`
|
||||
Liechtenstein int `json:"Liechtenstein"`
|
||||
Poland int `json:"Poland"`
|
||||
UnitedStatesVirginIslands int `json:"United States Virgin Islands"`
|
||||
Jordan int `json:"Jordan"`
|
||||
Bulgaria int `json:"Bulgaria"`
|
||||
Tunisia int `json:"Tunisia"`
|
||||
UnitedArabEmirates int `json:"United Arab Emirates"`
|
||||
Kenya int `json:"Kenya"`
|
||||
FrenchPolynesia int `json:"French Polynesia"`
|
||||
Lebanon int `json:"Lebanon"`
|
||||
Djibouti int `json:"Djibouti"`
|
||||
Azerbaijan int `json:"Azerbaijan"`
|
||||
CzechRepublic int `json:"Czech Republic"`
|
||||
SaintLucia int `json:"Saint Lucia"`
|
||||
Mauritania int `json:"Mauritania"`
|
||||
Guernsey int `json:"Guernsey"`
|
||||
Mayotte int `json:"Mayotte"`
|
||||
SanMarino int `json:"San Marino"`
|
||||
Israel int `json:"Israel"`
|
||||
Australia int `json:"Australia"`
|
||||
Tajikistan int `json:"Tajikistan"`
|
||||
Myanmar int `json:"Myanmar"`
|
||||
CentralAmerica int `json:"Central America"`
|
||||
EasternAfrica int `json:"Eastern Africa"`
|
||||
Cameroon int `json:"Cameroon"`
|
||||
Gibraltar int `json:"Gibraltar"`
|
||||
Cyprus int `json:"Cyprus"`
|
||||
Malaysia int `json:"Malaysia"`
|
||||
Oman int `json:"Oman"`
|
||||
Iceland int `json:"Iceland"`
|
||||
Armenia int `json:"Armenia"`
|
||||
Gabon int `json:"Gabon"`
|
||||
WesternAsia int `json:"Western Asia"`
|
||||
Polynesia int `json:"Polynesia"`
|
||||
Luxembourg int `json:"Luxembourg"`
|
||||
Brazil int `json:"Brazil"`
|
||||
TurksAndCaicosIslands int `json:"Turks and Caicos Islands"`
|
||||
Algeria int `json:"Algeria"`
|
||||
Slovenia int `json:"Slovenia"`
|
||||
Jersey int `json:"Jersey"`
|
||||
AntiguaAndBarbuda int `json:"Antigua and Barbuda"`
|
||||
Ecuador int `json:"Ecuador"`
|
||||
Colombia int `json:"Colombia"`
|
||||
Moldova int `json:"Moldova"`
|
||||
Vanuatu int `json:"Vanuatu"`
|
||||
UnitedStatesMinorOutlyingIslands int `json:"United States Minor Outlying Islands"`
|
||||
Italy int `json:"Italy"`
|
||||
Honduras int `json:"Honduras"`
|
||||
Haiti int `json:"Haiti"`
|
||||
Burundi int `json:"Burundi"`
|
||||
Singapore int `json:"Singapore"`
|
||||
FrenchGuiana int `json:"French Guiana"`
|
||||
AmericanSamoa int `json:"American Samoa"`
|
||||
Russia int `json:"Russia"`
|
||||
Netherlands int `json:"Netherlands"`
|
||||
China int `json:"China"`
|
||||
Martinique int `json:"Martinique"`
|
||||
SaintPierreAndMiquelon int `json:"Saint Pierre and Miquelon"`
|
||||
Kyrgyzstan int `json:"Kyrgyzstan"`
|
||||
Reunion int `json:"Reunion"`
|
||||
Bhutan int `json:"Bhutan"`
|
||||
Romania int `json:"Romania"`
|
||||
MiddleAfrica int `json:"Middle Africa"`
|
||||
Togo int `json:"Togo"`
|
||||
SouthernAsia int `json:"Southern Asia"`
|
||||
Philippines int `json:"Philippines"`
|
||||
CoteDIvoire int `json:"Cote d\'Ivoire"`
|
||||
Uzbekistan int `json:"Uzbekistan"`
|
||||
Asia int `json:"Asia"`
|
||||
BritishVirginIslands int `json:"British Virgin Islands"`
|
||||
Zimbabwe int `json:"Zimbabwe"`
|
||||
BritishIndianOceanTerritory int `json:"British Indian Ocean Territory"`
|
||||
Montenegro int `json:"Montenegro"`
|
||||
Indonesia int `json:"Indonesia"`
|
||||
Dominica int `json:"Dominica"`
|
||||
Benin int `json:"Benin"`
|
||||
Angola int `json:"Angola"`
|
||||
EasternEurope int `json:"Eastern Europe"`
|
||||
Portugal int `json:"Portugal"`
|
||||
BruneiDarussalam int `json:"Brunei Darussalam"`
|
||||
NewCaledonia int `json:"New Caledonia"`
|
||||
Grenada int `json:"Grenada"`
|
||||
Greece int `json:"Greece"`
|
||||
CaymanIslands int `json:"Cayman Islands"`
|
||||
Mongolia int `json:"Mongolia"`
|
||||
Latvia int `json:"Latvia"`
|
||||
Morocco int `json:"Morocco"`
|
||||
Guyana int `json:"Guyana"`
|
||||
Guatemala int `json:"Guatemala"`
|
||||
Chile int `json:"Chile"`
|
||||
Nepal int `json:"Nepal"`
|
||||
NorthernEurope int `json:"Northern Europe"`
|
||||
IsleOfMan int `json:"Isle of Man"`
|
||||
Ukraine int `json:"Ukraine"`
|
||||
Tanzania int `json:"Tanzania"`
|
||||
Ghana int `json:"Ghana"`
|
||||
HolySee int `json:"Holy See"`
|
||||
CentralAsia int `json:"Central Asia"`
|
||||
Anguilla int `json:"Anguilla"`
|
||||
SouthEasternAsia int `json:"South-Eastern Asia"`
|
||||
India int `json:"India"`
|
||||
Canada int `json:"Canada"`
|
||||
Maldives int `json:"Maldives"`
|
||||
Turkey int `json:"Turkey"`
|
||||
Belgium int `json:"Belgium"`
|
||||
Taiwan int `json:"Taiwan"`
|
||||
TrinidadAndTobago int `json:"Trinidad and Tobago"`
|
||||
SouthernEurope int `json:"Southern Europe"`
|
||||
SouthAfrica int `json:"South Africa"`
|
||||
Bermuda int `json:"Bermuda"`
|
||||
AlandIslands int `json:"Aland Islands"`
|
||||
Jamaica int `json:"Jamaica"`
|
||||
Turkmenistan int `json:"Turkmenistan"`
|
||||
Peru int `json:"Peru"`
|
||||
Germany int `json:"Germany"`
|
||||
Americas int `json:"Americas"`
|
||||
Fiji int `json:"Fiji"`
|
||||
HongKong int `json:"Hong Kong"`
|
||||
UnitedStates int `json:"United States"`
|
||||
Guinea int `json:"Guinea"`
|
||||
MicronesiaFederatedStatesOf int `json:"Micronesia, Federated States of"`
|
||||
Somalia int `json:"Somalia"`
|
||||
Chad int `json:"Chad"`
|
||||
Thailand int `json:"Thailand"`
|
||||
Kiribati int `json:"Kiribati"`
|
||||
EquatorialGuinea int `json:"Equatorial Guinea"`
|
||||
CostaRica int `json:"Costa Rica"`
|
||||
SaintMartinFrenchPart int `json:"Saint Martin (French part)"`
|
||||
Vietnam int `json:"Vietnam"`
|
||||
Nigeria int `json:"Nigeria"`
|
||||
Kuwait int `json:"Kuwait"`
|
||||
Croatia int `json:"Croatia"`
|
||||
Uruguay int `json:"Uruguay"`
|
||||
SriLanka int `json:"Sri Lanka"`
|
||||
CookIslands int `json:"Cook Islands"`
|
||||
TimorLeste int `json:"Timor-Leste"`
|
||||
UnitedKingdom int `json:"United Kingdom"`
|
||||
Switzerland int `json:"Switzerland"`
|
||||
Samoa int `json:"Samoa"`
|
||||
Spain int `json:"Spain"`
|
||||
WesternAfrica int `json:"Western Africa"`
|
||||
Venezuela int `json:"Venezuela"`
|
||||
BurkinaFaso int `json:"Burkina Faso"`
|
||||
Swaziland int `json:"Swaziland"`
|
||||
Caribbean int `json:"Caribbean"`
|
||||
Estonia int `json:"Estonia"`
|
||||
Niue int `json:"Niue"`
|
||||
SouthKorea int `json:"South Korea"`
|
||||
Austria int `json:"Austria"`
|
||||
Mozambique int `json:"Mozambique"`
|
||||
ElSalvador int `json:"El Salvador"`
|
||||
Monaco int `json:"Monaco"`
|
||||
Guam int `json:"Guam"`
|
||||
Lesotho int `json:"Lesotho"`
|
||||
Tonga int `json:"Tonga"`
|
||||
NorthernAfrica int `json:"Northern Africa"`
|
||||
WesternSahara int `json:"Western Sahara"`
|
||||
Hungary int `json:"Hungary"`
|
||||
Japan int `json:"Japan"`
|
||||
Europe int `json:"Europe"`
|
||||
Curacao int `json:"Curacao"`
|
||||
Belarus int `json:"Belarus"`
|
||||
Mauritius int `json:"Mauritius"`
|
||||
WesternEurope int `json:"Western Europe"`
|
||||
Albania int `json:"Albania"`
|
||||
NewZealand int `json:"New Zealand"`
|
||||
NorthernAmerica int `json:"Northern America"`
|
||||
SintMaartenDutchPart int `json:"Sint Maarten (Dutch part)"`
|
||||
Senegal int `json:"Senegal"`
|
||||
Macedonia int `json:"Macedonia"`
|
||||
Ethiopia int `json:"Ethiopia"`
|
||||
Egypt int `json:"Egypt"`
|
||||
SierraLeone int `json:"Sierra Leone"`
|
||||
Bolivia int `json:"Bolivia"`
|
||||
Oceania int `json:"Oceania"`
|
||||
Malta int `json:"Malta"`
|
||||
SaudiArabia int `json:"Saudi Arabia"`
|
||||
CapeVerde int `json:"Cape Verde"`
|
||||
Pakistan int `json:"Pakistan"`
|
||||
Gambia int `json:"Gambia"`
|
||||
Qatar int `json:"Qatar"`
|
||||
Ireland int `json:"Ireland"`
|
||||
Slovakia int `json:"Slovakia"`
|
||||
Serbia int `json:"Serbia"`
|
||||
Lithuania int `json:"Lithuania"`
|
||||
France int `json:"France"`
|
||||
BosniaAndHerzegovina int `json:"Bosnia and Herzegovina"`
|
||||
AustraliaAndNewZealand int `json:"Australia and New Zealand"`
|
||||
Rwanda int `json:"Rwanda"`
|
||||
Bangladesh int `json:"Bangladesh"`
|
||||
Nicaragua int `json:"Nicaragua"`
|
||||
Barbados int `json:"Barbados"`
|
||||
Norway int `json:"Norway"`
|
||||
SouthernAfrica int `json:"Southern Africa"`
|
||||
SaintVincentAndTheGrenadines int `json:"Saint Vincent and the Grenadines"`
|
||||
Botswana int `json:"Botswana"`
|
||||
Melanesia int `json:"Melanesia"`
|
||||
Macao int `json:"Macao"`
|
||||
DominicanRepublic int `json:"Dominican Republic"`
|
||||
Denmark int `json:"Denmark"`
|
||||
Uganda int `json:"Uganda"`
|
||||
Mexico int `json:"Mexico"`
|
||||
Suriname int `json:"Suriname"`
|
||||
Greenland int `json:"Greenland"`
|
||||
} `json:"location"`
|
||||
Timezone struct {
|
||||
int `json:""`
|
||||
AmericaSaoPaulo int `json:"America/Sao_Paulo"`
|
||||
AsiaVladivostok int `json:"Asia/Vladivostok"`
|
||||
EuropeBerlin int `json:"Europe/Berlin"`
|
||||
AfricaCairo int `json:"Africa/Cairo"`
|
||||
EuropeMoscow int `json:"Europe/Moscow"`
|
||||
PacificHonolulu int `json:"Pacific/Honolulu"`
|
||||
AustraliaHobart int `json:"Australia/Hobart"`
|
||||
EuropeLondon int `json:"Europe/London"`
|
||||
AsiaBaghdad int `json:"Asia/Baghdad"`
|
||||
AsiaShanghai int `json:"Asia/Shanghai"`
|
||||
AmericaTijuana int `json:"America/Tijuana"`
|
||||
AmericaManagua int `json:"America/Managua"`
|
||||
AsiaYerevan int `json:"Asia/Yerevan"`
|
||||
AsiaKamchatka int `json:"Asia/Kamchatka"`
|
||||
AfricaHarare int `json:"Africa/Harare"`
|
||||
AmericaNome int `json:"America/Nome"`
|
||||
AsiaYakutsk int `json:"Asia/Yakutsk"`
|
||||
AmericaChicago int `json:"America/Chicago"`
|
||||
AmericaHalifax int `json:"America/Halifax"`
|
||||
AmericaIndianaIndianapolis int `json:"America/Indiana/Indianapolis"`
|
||||
EuropeParis int `json:"Europe/Paris"`
|
||||
PacificFiji int `json:"Pacific/Fiji"`
|
||||
AsiaTehran int `json:"Asia/Tehran"`
|
||||
AmericaLaPaz int `json:"America/La_Paz"`
|
||||
AsiaTashkent int `json:"Asia/Tashkent"`
|
||||
AsiaBangkok int `json:"Asia/Bangkok"`
|
||||
PacificMidway int `json:"Pacific/Midway"`
|
||||
AmericaRecife int `json:"America/Recife"`
|
||||
AmericaBuenosAires int `json:"America/Buenos_Aires"`
|
||||
AustraliaAdelaide int `json:"Australia/Adelaide"`
|
||||
AsiaYangon int `json:"Asia/Yangon"`
|
||||
AsiaKatmandu int `json:"Asia/Katmandu"`
|
||||
AsiaAlmaty int `json:"Asia/Almaty"`
|
||||
AmericaPhoenix int `json:"America/Phoenix"`
|
||||
EuropePrague int `json:"Europe/Prague"`
|
||||
AmericaMexicoCity int `json:"America/Mexico_City"`
|
||||
AsiaTbilisi int `json:"Asia/Tbilisi"`
|
||||
AsiaJerusalem int `json:"Asia/Jerusalem"`
|
||||
EuropeLisbon int `json:"Europe/Lisbon"`
|
||||
AtlanticSouthGeorgia int `json:"Atlantic/South_Georgia"`
|
||||
AsiaKarachi int `json:"Asia/Karachi"`
|
||||
AustraliaPerth int `json:"Australia/Perth"`
|
||||
AustraliaDarwin int `json:"Australia/Darwin"`
|
||||
AsiaCalcutta int `json:"Asia/Calcutta"`
|
||||
AmericaBogota int `json:"America/Bogota"`
|
||||
AsiaKabul int `json:"Asia/Kabul"`
|
||||
AmericaNewYork int `json:"America/New_York"`
|
||||
AtlanticAzores int `json:"Atlantic/Azores"`
|
||||
AsiaKrasnoyarsk int `json:"Asia/Krasnoyarsk"`
|
||||
EET int `json:"EET"`
|
||||
PacificAuckland int `json:"Pacific/Auckland"`
|
||||
EuropeMinsk int `json:"Europe/Minsk"`
|
||||
AfricaCasablanca int `json:"Africa/Casablanca"`
|
||||
AmericaCaracas int `json:"America/Caracas"`
|
||||
EuropeKiev int `json:"Europe/Kiev"`
|
||||
AsiaMagadan int `json:"Asia/Magadan"`
|
||||
AmericaRegina int `json:"America/Regina"`
|
||||
PacificGuam int `json:"Pacific/Guam"`
|
||||
AsiaIrkutsk int `json:"Asia/Irkutsk"`
|
||||
AfricaAlgiers int `json:"Africa/Algiers"`
|
||||
AmericaStJohns int `json:"America/St_Johns"`
|
||||
AmericaFortaleza int `json:"America/Fortaleza"`
|
||||
AmericaDenver int `json:"America/Denver"`
|
||||
AmericaIndianaKnox int `json:"America/Indiana/Knox"`
|
||||
EtcUTC int `json:"Etc/UTC"`
|
||||
AustraliaSydney int `json:"Australia/Sydney"`
|
||||
PacificApia int `json:"Pacific/Apia"`
|
||||
AsiaTokyo int `json:"Asia/Tokyo"`
|
||||
AsiaIstanbul int `json:"Asia/Istanbul"`
|
||||
AsiaOmsk int `json:"Asia/Omsk"`
|
||||
AustraliaBrisbane int `json:"Australia/Brisbane"`
|
||||
AmericaLosAngeles int `json:"America/Los_Angeles"`
|
||||
AsiaYekaterinburg int `json:"Asia/Yekaterinburg"`
|
||||
EuropeAthens int `json:"Europe/Athens"`
|
||||
} `json:"timezone"`
|
||||
ConnectPrice struct {
|
||||
Num4 int `json:"4"`
|
||||
Num6 int `json:"6"`
|
||||
Zero2 int `json:"0-2"`
|
||||
} `json:"connectPrice"`
|
||||
ContractToHire struct {
|
||||
True int `json:"true"`
|
||||
False int `json:"false"`
|
||||
} `json:"contractToHire"`
|
||||
Categories []interface{} `json:"categories"`
|
||||
Subcategories []interface{} `json:"subcategories"`
|
||||
PaymentVerified struct {
|
||||
Num1 int `json:"1"`
|
||||
} `json:"paymentVerified"`
|
||||
} `json:"facets"`
|
||||
IsSearchWithEmptyParams bool `json:"isSearchWithEmptyParams"`
|
||||
CurrentQuery struct {
|
||||
Sort string `json:"sort"`
|
||||
} `json:"currentQuery"`
|
||||
QueryParsedParams struct {
|
||||
Sort string `json:"sort"`
|
||||
Paging string `json:"paging"`
|
||||
} `json:"queryParsedParams"`
|
||||
PageTitle string `json:"pageTitle"`
|
||||
JobSearchError bool `json:"jobSearchError"`
|
||||
RssLink string `json:"rssLink"`
|
||||
AtomLink string `json:"atomLink"`
|
||||
SmartSearch struct {
|
||||
DownloadTeamApplication bool `json:"downloadTeamApplication"`
|
||||
} `json:"smartSearch"`
|
||||
} `json:"searchResults"`
|
||||
Filters struct {
|
||||
Q string `json:"q"`
|
||||
Sort string `json:"sort"`
|
||||
Skills struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Options []interface{} `json:"options"`
|
||||
} `json:"skills"`
|
||||
Categories []struct {
|
||||
URLName string `json:"urlName"`
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
ActiveLabel string `json:"activeLabel"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
Subcategories []struct {
|
||||
URLName string `json:"urlName"`
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
ActiveLabel string `json:"activeLabel"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"subcategories"`
|
||||
} `json:"categories"`
|
||||
JobType struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"jobType"`
|
||||
ContractorTier struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"contractorTier"`
|
||||
ClientHires struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"clientHires"`
|
||||
Proposals struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
ActiveLabel string `json:"activeLabel"`
|
||||
} `json:"options"`
|
||||
} `json:"proposals"`
|
||||
Amount struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
DisabledJobTypeValue string `json:"disabledJobTypeValue"`
|
||||
DisabledMessage string `json:"disabledMessage"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
CustomAmount struct {
|
||||
Value interface{} `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
CustomMin interface{} `json:"customMin"`
|
||||
CustomMax interface{} `json:"customMax"`
|
||||
} `json:"customAmount"`
|
||||
} `json:"amount"`
|
||||
Workload struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
DisabledJobTypeValue string `json:"disabledJobTypeValue"`
|
||||
DisabledMessage string `json:"disabledMessage"`
|
||||
} `json:"workload"`
|
||||
DurationV3 struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
DisabledJobTypeValue string `json:"disabledJobTypeValue"`
|
||||
DisabledMessage string `json:"disabledMessage"`
|
||||
} `json:"duration_v3"`
|
||||
PreviousClients struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Value string `json:"value"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"previousClients"`
|
||||
PaymentVerified struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Value string `json:"value"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"paymentVerified"`
|
||||
TalentClouds struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Options []interface{} `json:"options"`
|
||||
} `json:"talentClouds"`
|
||||
UserLocationMatch struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Value string `json:"value"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"userLocationMatch"`
|
||||
Occupation struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Options []interface{} `json:"options"`
|
||||
} `json:"occupation"`
|
||||
OntologySkills struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Options []interface{} `json:"options"`
|
||||
} `json:"ontologySkills"`
|
||||
HourlyRate struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Options []interface{} `json:"options"`
|
||||
CustomHourlyRate struct {
|
||||
Value interface{} `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
CustomMin interface{} `json:"customMin"`
|
||||
CustomMax interface{} `json:"customMax"`
|
||||
} `json:"customHourlyRate"`
|
||||
} `json:"hourly_rate"`
|
||||
FreelancersNeeded struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"freelancers_needed"`
|
||||
Location struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Options []struct {
|
||||
UID string `json:"uid"`
|
||||
Label string `json:"label"`
|
||||
SubRegionUID string `json:"subRegionUid,omitempty"`
|
||||
RegionUID string `json:"regionUid,omitempty"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"location"`
|
||||
Timezones struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"timezones"`
|
||||
Connects struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
LoggingLabel string `json:"loggingLabel"`
|
||||
Options []struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"options"`
|
||||
} `json:"connects"`
|
||||
ContractToHire struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
LoggingSublocation string `json:"loggingSublocation"`
|
||||
Value string `json:"value"`
|
||||
Checked bool `json:"checked"`
|
||||
Count int `json:"count"`
|
||||
} `json:"contractToHire"`
|
||||
} `json:"filters"`
|
||||
}
|
||||
104
upwork/upworkClient.go
Normal file
104
upwork/upworkClient.go
Normal file
@ -0,0 +1,104 @@
|
||||
package upwork
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"scrapers/network"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Upwork struct {
|
||||
UpworkHttpClient *network.Client
|
||||
}
|
||||
|
||||
type GraphQLRequest struct {
|
||||
Query string `json:"query"`
|
||||
Variables map[string]interface{} `json:"variables"`
|
||||
}
|
||||
|
||||
func (u Upwork) SendRequest(query string, variables map[string]interface{}) (string, error) {
|
||||
url := "https://www.upwork.com/api/graphql/v1"
|
||||
|
||||
requestBody := GraphQLRequest{
|
||||
Query: query,
|
||||
Variables: variables,
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := u.UpworkHttpClient.PostRequest(url, string(jsonBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
func readGraphQLQuery(filename string) (string, error) {
|
||||
content, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func mergeMaps(m1, m2 map[string]string) map[string]string {
|
||||
for k, v := range m2 {
|
||||
m1[k] = v
|
||||
}
|
||||
return m1
|
||||
}
|
||||
|
||||
func readEnv(filename string) (map[string]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
m := make(map[string]string)
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
parts := strings.SplitN(scanner.Text(), "=", 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
m[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func InitUpwork() *Upwork {
|
||||
headers := map[string]string{
|
||||
"authority": "www.upwork.com",
|
||||
"accept": "application/json, text/plain",
|
||||
"accept-language": "en",
|
||||
"content-type": "application/json",
|
||||
"cache-control": "no-cache",
|
||||
"pragma": "no-cache",
|
||||
"referer": "https://www.upwork.com/search/jobs/",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"sec-gpc": "1",
|
||||
"x-requested-with": "XMLHttpRequest",
|
||||
}
|
||||
|
||||
auth_headers, err := readEnv("upwork/.env")
|
||||
if err != nil {
|
||||
log.Fatal("Could not read .env file, please add .env file in upwork folder")
|
||||
}
|
||||
headers = mergeMaps(auth_headers, headers)
|
||||
|
||||
client := network.InitClient(headers)
|
||||
upwork := Upwork{
|
||||
UpworkHttpClient: client,
|
||||
}
|
||||
|
||||
return &upwork
|
||||
}
|
||||
154
upwork/upworkPipeline.go
Normal file
154
upwork/upworkPipeline.go
Normal file
@ -0,0 +1,154 @@
|
||||
package upwork
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UpworkPipeLine struct {
|
||||
upworkClient *Upwork
|
||||
}
|
||||
|
||||
func InitPipeline() *UpworkPipeLine {
|
||||
return &UpworkPipeLine{
|
||||
upworkClient: InitUpwork(),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UpworkPipeLine) appendToJSONL(data []interface{}, filename string) error {
|
||||
file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
for _, item := range data {
|
||||
jsonLine, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := file.Write(append(jsonLine, '\n')); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpworkPipeLine) isResponseValid(data string) bool {
|
||||
var resp map[string]interface{}
|
||||
err := json.Unmarshal([]byte(data), &resp)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, hasErrors := resp["errors"]
|
||||
return !hasErrors
|
||||
}
|
||||
|
||||
func (u *UpworkPipeLine) getTotalDocuments(query string, variables map[string]interface{}) (int, error) {
|
||||
resp, err := u.upworkClient.SendRequest(query, variables)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !u.isResponseValid(resp) {
|
||||
return 0, fmt.Errorf("invalid response")
|
||||
}
|
||||
|
||||
var graphQLResp map[string]interface{}
|
||||
err = json.Unmarshal([]byte(resp), &graphQLResp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
paging, ok := graphQLResp["data"].(map[string]interface{})["search"].(map[string]interface{})["universalSearchNuxt"].(map[string]interface{})["userJobSearchV1"].(map[string]interface{})["paging"].(map[string]interface{})
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unexpected response structure")
|
||||
}
|
||||
|
||||
total, ok := paging["total"].(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("total is not a number")
|
||||
}
|
||||
|
||||
return int(total), nil
|
||||
}
|
||||
|
||||
func (u *UpworkPipeLine) handleRequest(query string, variables map[string]interface{}, filename string) error {
|
||||
resp, err := u.upworkClient.SendRequest(query, variables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !u.isResponseValid(resp) {
|
||||
//print the response
|
||||
fmt.Println(resp)
|
||||
return fmt.Errorf("invalid response returned")
|
||||
|
||||
}
|
||||
|
||||
var graphQLResp map[string]interface{}
|
||||
err = json.Unmarshal([]byte(resp), &graphQLResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
results, ok := graphQLResp["data"].(map[string]interface{})["search"].(map[string]interface{})["universalSearchNuxt"].(map[string]interface{})["userJobSearchV1"].(map[string]interface{})["results"].([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected response structure")
|
||||
}
|
||||
|
||||
return u.appendToJSONL(results, filename)
|
||||
}
|
||||
|
||||
func (u *UpworkPipeLine) Run(userQuery string) error {
|
||||
query, err := readGraphQLQuery("JobSearchQuery.gql")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"requestVariables": map[string]interface{}{
|
||||
"userQuery": userQuery,
|
||||
"sort": "recency",
|
||||
"highlight": true,
|
||||
"paging": map[string]interface{}{
|
||||
"offset": 0,
|
||||
"count": 50,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
total_docs, err := u.getTotalDocuments(query, variables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s has a total of %d jobs\n", userQuery, total_docs)
|
||||
|
||||
iterations := total_docs / 50
|
||||
// if iterations > 100 {
|
||||
// iterations = 100
|
||||
// }
|
||||
|
||||
fmt.Printf("A total of %d iterations will be performed\n", iterations)
|
||||
|
||||
now := time.Now()
|
||||
dateString := now.Format("2006-01-02")
|
||||
filename := fmt.Sprintf("upwork_jobs_%s.jsonl", dateString)
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
fmt.Printf("Processing iteration %d of %d\n", i+1, iterations)
|
||||
variables["requestVariables"].(map[string]interface{})["paging"].(map[string]interface{})["offset"] = i * 50
|
||||
err := u.handleRequest(query, variables, filename)
|
||||
if err != nil {
|
||||
log.Printf("Error in iteration %d: %v", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Job data has been written to %s\n", filename)
|
||||
return nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user